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

7
.gitignore vendored
View File

@ -6,9 +6,10 @@ unknown_errors.txt
# Pyrogram generated code
pyrogram/errors/exceptions/
pyrogram/api/functions/
pyrogram/api/types/
pyrogram/api/all.py
pyrogram/raw/functions/
pyrogram/raw/types/
pyrogram/raw/base/
pyrogram/raw/all.py
# PyCharm stuff
.idea/

View File

@ -5,6 +5,6 @@ recursive-include pyrogram mime.types schema.sql
## Exclude
prune pyrogram/errors/exceptions
prune pyrogram/api/functions
prune pyrogram/api/types
exclude pyrogram/api/all.py
prune pyrogram/raw/functions
prune pyrogram/raw/types
exclude pyrogram/raw/all.py

View File

@ -21,25 +21,22 @@
## Pyrogram
``` python
from pyrogram import Client, Filters
from pyrogram import Client, filters
app = Client("my_account")
@app.on_message(Filters.private)
def hello(client, message):
message.reply_text("Hello {}".format(message.from_user.first_name))
@app.on_message(filters.private)
async def hello(client, message):
await message.reply_text(f"Hello {message.from_user.mention}")
app.run()
```
**Pyrogram** is an elegant, easy-to-use [Telegram](https://telegram.org/) client library and framework written from the
ground up in Python and C. It enables you to easily create custom apps for both user and bot identities (bot API alternative) via the [MTProto API](https://core.telegram.org/api#telegram-api).
> [Pyrogram in fully-asynchronous mode is also available »](https://github.com/pyrogram/pyrogram/issues/181)
>
> [Working PoC of Telegram voice calls using Pyrogram »](https://github.com/bakatrouble/pytgvoip)
**Pyrogram** is a modern, elegant and easy-to-use [Telegram](https://telegram.org/) framework written from the ground up
in Python and C. It enables you to easily create custom apps for both user and bot identities (bot API alternative) via
the [MTProto API](https://core.telegram.org/api#telegram-api).
### Features
@ -47,8 +44,9 @@ ground up in Python and C. It enables you to easily create custom apps for both
- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way.
- **Fast**: Crypto parts are boosted up by [TgCrypto](https://github.com/pyrogram/tgcrypto), a high-performance library
written in pure C.
- **Documented**: Pyrogram API methods, types and public interfaces are well documented.
- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
- **Asynchronous**: Allows both synchronous and asynchronous usages to fit all usage needs.
- **Documented**: API methods, types and public interfaces are all [well documented](https://docs.pyrogram.org).
- **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support.
- **Updated**, to make use of the latest Telegram API version and features.
- **Bot API-like**: Similar to the Bot API in its simplicity, but much more powerful and detailed.
- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code.
@ -56,7 +54,7 @@ ground up in Python and C. It enables you to easily create custom apps for both
### Requirements
- Python 3.5.3 or higher.
- Python 3.6 or higher.
- A [Telegram API key](https://docs.pyrogram.org/intro/setup#api-keys).
### Installing
@ -67,17 +65,9 @@ pip3 install pyrogram
### Resources
- The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.org.
- Reading [Examples in this repository](https://github.com/pyrogram/pyrogram/tree/master/examples) is also a good way
for learning how Pyrogram works.
- Seeking extra help? Don't be shy, come join and ask our [Community](https://t.me/PyrogramChat)!
- For other requests you can send an [Email](mailto:dan@pyrogram.org) or a [Message](https://t.me/haskell).
### Contributing
Pyrogram is brand new, and **you are welcome to try it and help make it even better** by either submitting pull
requests or reporting issues/bugs as well as suggesting best practices, ideas, enhancements on both code
and documentation. Any help is appreciated!
- The docs contain lots of resources to help you get started with Pyrogram: https://docs.pyrogram.org.
- Seeking extra help? Come join and ask our community: https://t.me/pyrogram.
- For other kind of inquiries, you can send a [message](https://t.me/haskell) or an [e-mail](mailto:dan@pyrogram.org).
### Copyright & License

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

View File

@ -2,7 +2,7 @@
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXOPTS = -j $(shell nproc --all)
SPHINXBUILD = sphinx-build
SPHINXPROJ = Pyrogram
SOURCEDIR = source
@ -17,4 +17,7 @@ help:
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
lhtml: # live html
sphinx-autobuild -H $(shell ipconfig getifaddr en3) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS)

View File

@ -3,4 +3,5 @@ sphinx_rtd_theme
sphinx_copybutton
sphinx_tabs
pypandoc
requests
requests
sphinx-autobuild

View File

@ -46,7 +46,7 @@ with open("sitemap.xml", "w") as f:
def search(path):
try:
for j in os.listdir(path):
search("{}/{}".format(path, j))
search(f"{path}/{j}")
except NotADirectoryError:
if not path.endswith(".rst"):
return
@ -58,7 +58,7 @@ with open("sitemap.xml", "w") as f:
else:
folder = path[0]
path = "{}{}".format(canonical, "/".join(path))[:-len(".rst")]
path = f"{canonical}{'/'.join(path)}"[:-len(".rst")]
if path.endswith("index"):
path = path[:-len("index")]
@ -71,11 +71,11 @@ with open("sitemap.xml", "w") as f:
urls.sort(key=lambda x: x[3], reverse=True)
for i in urls:
f.write(" <url>\n")
f.write(" <loc>{}</loc>\n".format(i[0]))
f.write(" <lastmod>{}</lastmod>\n".format(i[1]))
f.write(" <changefreq>{}</changefreq>\n".format(i[2]))
f.write(" <priority>{}</priority>\n".format(i[3]))
f.write(" </url>\n\n")
f.write(f" <url>\n")
f.write(f" <loc>{i[0]}</loc>\n")
f.write(f" <lastmod>{i[1]}</lastmod>\n")
f.write(f" <changefreq>{i[2]}</changefreq>\n")
f.write(f" <priority>{i[3]}</priority>\n")
f.write(f" </url>\n\n")
f.write("</urlset>")

View File

@ -26,6 +26,7 @@ functions.
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -1,78 +0,0 @@
RPC Errors
==========
All Pyrogram API errors live inside the ``errors`` sub-package: ``pyrogram.errors``.
The errors ids listed here are shown as *UPPER_SNAKE_CASE*, but the actual exception names to import from Pyrogram
follow the usual *PascalCase* convention.
.. code-block:: python
:emphasize-lines: 1, 5
from pyrogram.errors import FloodWait
try:
...
except FloodWait as e:
...
.. contents:: Contents
:backlinks: none
:local:
-----
303 - SeeOther
--------------
.. csv-table::
:file: ../../../compiler/error/source/303_SEE_OTHER.tsv
:delim: tab
:header-rows: 1
400 - BadRequest
----------------
.. csv-table::
:file: ../../../compiler/error/source/400_BAD_REQUEST.tsv
:delim: tab
:header-rows: 1
401 - Unauthorized
------------------
.. csv-table::
:file: ../../../compiler/error/source/401_UNAUTHORIZED.tsv
:delim: tab
:header-rows: 1
403 - Forbidden
---------------
.. csv-table::
:file: ../../../compiler/error/source/403_FORBIDDEN.tsv
:delim: tab
:header-rows: 1
406 - NotAcceptable
-------------------
.. csv-table::
:file: ../../../compiler/error/source/406_NOT_ACCEPTABLE.tsv
:delim: tab
:header-rows: 1
420 - Flood
-----------
.. csv-table::
:file: ../../../compiler/error/source/420_FLOOD.tsv
:delim: tab
:header-rows: 1
500 - InternalServerError
-------------------------
.. csv-table::
:file: ../../../compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv
:delim: tab
:header-rows: 1

View File

@ -0,0 +1,7 @@
400 - BadRequest
----------------
.. csv-table::
:file: ../../../../compiler/error/source/400_BAD_REQUEST.tsv
:delim: tab
:header-rows: 1

View File

@ -0,0 +1,7 @@
420 - Flood
-----------
.. csv-table::
:file: ../../../../compiler/error/source/420_FLOOD.tsv
:delim: tab
:header-rows: 1

View File

@ -0,0 +1,7 @@
403 - Forbidden
---------------
.. csv-table::
:file: ../../../../compiler/error/source/403_FORBIDDEN.tsv
:delim: tab
:header-rows: 1

View File

@ -0,0 +1,37 @@
RPC Errors
==========
All Pyrogram API errors live inside the ``errors`` sub-package: ``pyrogram.errors``.
The errors ids listed here are shown as *UPPER_SNAKE_CASE*, but the actual exception names to import from Pyrogram
follow the usual *PascalCase* convention.
.. code-block:: python
from pyrogram.errors import FloodWait
try:
...
except FloodWait as e:
...
.. hlist::
:columns: 1
- :doc:`see-other`
- :doc:`bad-request`
- :doc:`unauthorized`
- :doc:`forbidden`
- :doc:`not-acceptable`
- :doc:`flood`
- :doc:`internal-server-error`
.. toctree::
:hidden:
see-other
bad-request
unauthorized
forbidden
not-acceptable
flood
internal-server-error

View File

@ -0,0 +1,7 @@
500 - InternalServerError
-------------------------
.. csv-table::
:file: ../../../../compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv
:delim: tab
:header-rows: 1

View File

@ -0,0 +1,7 @@
406 - NotAcceptable
-------------------
.. csv-table::
:file: ../../../../compiler/error/source/406_NOT_ACCEPTABLE.tsv
:delim: tab
:header-rows: 1

View File

@ -0,0 +1,7 @@
303 - SeeOther
--------------
.. csv-table::
:file: ../../../../compiler/error/source/303_SEE_OTHER.tsv
:delim: tab
:header-rows: 1

View File

@ -0,0 +1,7 @@
401 - Unauthorized
------------------
.. csv-table::
:file: ../../../../compiler/error/source/401_UNAUTHORIZED.tsv
:delim: tab
:header-rows: 1

View File

@ -1,8 +1,11 @@
Update Filters
==============
Filters are objects that can be used to filter the content of incoming updates.
:doc:`Read more about how filters work <../topics/use-filters>`.
Details
-------
.. autoclass:: pyrogram.Filters
.. automodule:: pyrogram.filters
:members:

View File

@ -7,7 +7,8 @@ For a much more convenient way of registering callback functions have a look at
.. code-block:: python
:emphasize-lines: 1, 10
from pyrogram import Client, MessageHandler
from pyrogram import Client
from pyrogram.handlers import MessageHandler
app = Client("my_account")
@ -22,11 +23,12 @@ For a much more convenient way of registering callback functions have a look at
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
.. currentmodule:: pyrogram
.. currentmodule:: pyrogram.handlers
Index
-----

View File

@ -12,6 +12,7 @@ This FAQ page provides answers to common questions about Pyrogram and, to some e
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -10,6 +10,7 @@ general. Some words may as well link to dedicated articles in case the topic is
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -1,6 +1,95 @@
Welcome to Pyrogram
===================
.. raw:: html
<div align="center">
<a href="/">
<div><img src="_static/pyrogram.png" alt="Pyrogram Logo" width="420"></div>
</a>
</div>
<p align="center">
<b>Telegram MTProto API Framework for Python</b>
<br>
<a href="https://github.com/pyrogram/pyrogram">
Source Code
</a>
<a href="https://github.com/pyrogram/pyrogram/releases">
Releases
</a>
<a href="https://t.me/Pyrogram">
Community
</a>
</p>
.. code-block:: python
from pyrogram import Client, filters
app = Client("my_account")
@app.on_message(filters.private)
async def hello(client, message):
await message.reply_text(f"Hello {message.from_user.mention}")
app.run()
**Pyrogram** is a modern, elegant and easy-to-use Telegram_ framework written from the ground up in Python and C.
It enables you to easily create custom apps for both user and bot identities (bot API alternative) via the
:doc:`MTProto API <topics/mtproto-vs-botapi>`.
.. _Telegram: https://telegram.org
How the Documentation is Organized
----------------------------------
Contents are organized into sections composed of self-contained topics which can be all accessed from the sidebar, or by
following them in order using the :guilabel:`Next` button at the end of each page. Here below you can, instead, find a
list of the most relevant pages for a quick access.
First Steps
^^^^^^^^^^^
.. hlist::
:columns: 2
- :doc:`Quick Start <intro/quickstart>`: Overview to get you started quickly.
- :doc:`Calling Methods <start/invoking>`: How to call Pyrogram's methods.
- :doc:`Handling Updates <start/updates>`: How to handle Telegram updates.
- :doc:`Error Handling <start/errors>`: How to handle API errors correctly.
API Reference
^^^^^^^^^^^^^
.. hlist::
:columns: 2
- :doc:`Pyrogram Client <api/client>`: Reference details about the Client class.
- :doc:`Available Methods <api/methods/index>`: List of available high-level methods.
- :doc:`Available Types <api/types/index>`: List of available high-level types.
- :doc:`Bound Methods <api/bound-methods/index>`: List of convenient bound methods.
Meta
^^^^
.. hlist::
:columns: 2
- :doc:`Pyrogram FAQ <faq>`: Answers to common Pyrogram questions.
- :doc:`Pyrogram Glossary <glossary>`: List of words with brief explanations.
- :doc:`Powered by Pyrogram <powered-by>`: Collection of Pyrogram Projects.
- :doc:`Support Pyrogram <support-pyrogram>`: Ways to show your appreciation.
- :doc:`About the License <license>`: Information about the Project license.
- :doc:`Release Notes <releases/index>`: Release notes for Pyrogram releases.
Last updated on |today|
.. toctree::
:hidden:
:caption: Introduction
@ -17,6 +106,7 @@ Welcome to Pyrogram
start/invoking
start/updates
start/errors
start/examples/index
.. toctree::
:hidden:
@ -28,7 +118,7 @@ Welcome to Pyrogram
api/bound-methods/index
api/handlers
api/decorators
api/errors
api/errors/index
api/filters
.. toctree::
@ -71,92 +161,4 @@ Welcome to Pyrogram
telegram/functions/index
telegram/types/index
.. raw:: html
<div align="center">
<a href="/">
<div><img src="_static/pyrogram.png" alt="Pyrogram Logo" width="420"></div>
</a>
</div>
<p align="center">
<b>Telegram MTProto API Framework for Python</b>
<br>
<a href="https://github.com/pyrogram/pyrogram">
Source Code
</a>
<a href="https://github.com/pyrogram/pyrogram/releases">
Releases
</a>
<a href="https://t.me/Pyrogram">
Community
</a>
</p>
.. code-block:: python
from pyrogram import Client, Filters
app = Client("my_account")
@app.on_message(Filters.private)
def hello(client, message):
message.reply_text(f"Hello {message.from_user.first_name}")
app.run()
**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and
C. It enables you to easily create custom apps for both user and bot identities (bot API alternative) via the
:doc:`MTProto API <topics/mtproto-vs-botapi>`.
.. _Telegram: https://telegram.org
How the Documentation is Organized
----------------------------------
Contents are organized into self-contained topics and can be all accessed from the sidebar, or by following them in
order using the :guilabel:`Next` button at the end of each page. Here below you can, instead, find a list of the most
relevant pages for a quick access.
First Steps
^^^^^^^^^^^
.. hlist::
:columns: 2
- :doc:`Quick Start <intro/quickstart>`: Overview to get you started quickly.
- :doc:`Calling Methods <start/invoking>`: How to call Pyrogram's methods.
- :doc:`Handling Updates <start/updates>`: How to handle Telegram updates.
- :doc:`Error Handling <start/errors>`: How to handle API errors correctly.
API Reference
^^^^^^^^^^^^^
.. hlist::
:columns: 2
- :doc:`Pyrogram Client <api/client>`: Reference details about the Client class.
- :doc:`Available Methods <api/methods/index>`: List of available high-level methods.
- :doc:`Available Types <api/types/index>`: List of available high-level types.
- :doc:`Bound Methods <api/bound-methods/index>`: List of convenient bound methods.
Meta
^^^^
.. hlist::
:columns: 2
- :doc:`Pyrogram FAQ <faq>`: Answers to common Pyrogram questions.
- :doc:`Pyrogram Glossary <glossary>`: List of words with brief explanations.
- :doc:`Powered by Pyrogram <powered-by>`: Collection of Pyrogram Projects.
- :doc:`Support Pyrogram <support-pyrogram>`: Ways to show your appreciation.
- :doc:`About the License <license>`: Information about the Project license.
- :doc:`Release Notes <releases/index>`: Release notes for Pyrogram releases.
Last updated on |today|
telegram/base/index

View File

@ -1,18 +1,19 @@
Install Guide
=============
Being a Python library, **Pyrogram** requires Python to be installed in your system.
Being a modern Python library, **Pyrogram** requires Python 3.6+ to be installed in your system.
We recommend using the latest versions of both Python 3 and pip.
- Get **Python 3** from https://www.python.org/downloads/ (or with your package manager)
- Get **Python 3** from https://www.python.org/downloads/ (or with your package manager).
- Get **pip** by following the instructions at https://pip.pypa.io/en/latest/installing/.
.. important::
Pyrogram supports **Python 3** only, starting from version 3.5.3. **PyPy** is supported too.
Pyrogram supports **Python 3** only, starting from version 3.6. **PyPy** is supported too.
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@ -46,43 +47,6 @@ the link):
$ pip3 install -U https://github.com/pyrogram/pyrogram/archive/develop.zip
Asynchronous
------------
Pyrogram heavily depends on IO-bound network code (it's a cloud-based messaging framework after all), and here's
where asyncio shines the most by providing extra performance and efficiency while running on a single OS-level thread
only.
**A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5.3 or higher is required).
Use this command to install (note "asyncio.zip" in the link):
.. code-block:: text
$ pip3 install -U https://github.com/pyrogram/pyrogram/archive/asyncio.zip
Pyrogram's API remains the same and features are kept up to date from the non-async, default develop branch, but you
are obviously required Python asyncio knowledge in order to take full advantage of it.
.. tip::
The idea to turn Pyrogram fully asynchronous is still under consideration, but is wise to expect that in future this
would be the one and only way to work with Pyrogram.
You can start using Pyrogram Async variant right now as an excuse to learn more about asynchronous programming and
do experiments with it!
.. raw:: html
<script async
src="https://telegram.org/js/telegram-widget.js?4"
data-telegram-post="Pyrogram/4"
data-width="100%">
</script>
.. centered:: Subscribe to `@Pyrogram <https://t.me/Pyrogram>`_ for news and announcements
Verifying
---------

View File

@ -1,8 +1,8 @@
Quick Start
===========
The next few steps serve as a quick start for all new Pyrogrammers that want to get something done as fast as possible.
Let's go!
The next few steps serve as a quick start for all new Pyrogrammers that want to see Pyrogram in action as fast as
possible. Let's go!
Get Pyrogram Real Fast
----------------------

View File

@ -6,6 +6,7 @@ project with the library. Let's see how it's done.
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -11,6 +11,7 @@ This is a collection of remarkable projects made with Pyrogram.
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -6,6 +6,7 @@ API calls. This section provides all the information you need in order to author
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@ -41,7 +42,7 @@ keep the session alive, Pyrogram won't ask you again to enter your phone number.
.. important::
Your ``*.session`` files are personal and must be kept secret.
Your ``*.session`` file is personal and must be kept secret.
.. note::

View File

@ -10,6 +10,7 @@ to control the behaviour of your application. Pyrogram errors all live inside th
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@ -17,9 +18,8 @@ to control the behaviour of your application. Pyrogram errors all live inside th
RPCError
--------
The father of all errors is named ``RPCError``. This error exists in form of a Python exception which is directly
subclass-ed from Python's main ``Exception`` and is able to catch all Telegram API related errors. This error is raised
every time a method call against Telegram's API was unsuccessful.
The father of all errors is named ``RPCError`` and is able to catch all Telegram API related errors.
This error is raised every time a method call against Telegram's API was unsuccessful.
.. code-block:: python
@ -34,19 +34,19 @@ Error Categories
----------------
The ``RPCError`` packs together all the possible errors Telegram could raise, but to make things tidier, Pyrogram
provides categories of errors, which are named after the common HTTP errors and are subclass-ed from the RPCError:
provides categories of errors, which are named after the common HTTP errors and are subclass-ed from the ``RPCError``:
.. code-block:: python
from pyrogram.errors import BadRequest, Forbidden, ...
- `303 - SeeOther <../api/errors#seeother>`_
- `400 - BadRequest <../api/errors#badrequest>`_
- `401 - Unauthorized <../api/errors#unauthorized>`_
- `403 - Forbidden <../api/errors#forbidden>`_
- `406 - NotAcceptable <../api/errors#notacceptable>`_
- `420 - Flood <../api/errors#flood>`_
- `500 - InternalServerError <../api/errors#internalservererror>`_
- :doc:`303 - SeeOther <../api/errors/see-other>`
- :doc:`400 - BadRequest <../api/errors/bad-request>`
- :doc:`401 - Unauthorized <../api/errors/unauthorized>`
- :doc:`403 - Forbidden <../api/errors/forbidden>`
- :doc:`406 - NotAcceptable <../api/errors/not-acceptable>`
- :doc:`420 - Flood <../api/errors/flood>`
- :doc:`500 - InternalServerError <../api/errors/internal-server-error>`
Single Errors
-------------
@ -59,7 +59,7 @@ issue. For example:
from pyrogram.errors import FloodWait
These errors subclass directly from the category of errors they belong to, which in turn subclass from the father
RPCError, thus building a class of error hierarchy such as this:
``RPCError``, thus building a class of error hierarchy such as this:
- RPCError
- BadRequest

View File

@ -0,0 +1,61 @@
bot_keyboards
=============
This example will show you how to send normal and inline keyboards (as bot).
You must log-in as a regular bot in order to send keyboards (use the token from @BotFather).
Any attempt in sending keyboards with a user account will be simply ignored by the server.
send_message() is used as example, but a keyboard can be sent with any other send_* methods,
like send_audio(), send_document(), send_location(), etc...
.. code-block:: python
from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
# Create a client using your bot token
app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
with app:
app.send_message(
"haskell", # Edit this
"This is a ReplyKeyboardMarkup example",
reply_markup=ReplyKeyboardMarkup(
[
["A", "B", "C", "D"], # First row
["E", "F", "G"], # Second row
["H", "I"], # Third row
["J"] # Fourth row
],
resize_keyboard=True # Make the keyboard smaller
)
)
app.send_message(
"haskell", # Edit this
"This is a InlineKeyboardMarkup example",
reply_markup=InlineKeyboardMarkup(
[
[ # First row
InlineKeyboardButton( # Generates a callback query when pressed
"Button",
callback_data="data"
),
InlineKeyboardButton( # Opens a web URL
"URL",
url="https://docs.pyrogram.org"
),
],
[ # Second row
InlineKeyboardButton( # Opens the inline interface
"Choose chat",
switch_inline_query="pyrogram"
),
InlineKeyboardButton( # Opens the inline interface in the current chat
"Inline here",
switch_inline_query_current_chat="pyrogram"
)
]
]
)
)

View File

@ -0,0 +1,19 @@
callback_queries
================
This example shows how to handle callback queries, i.e.: queries coming from inline button presses.
It uses the @on_callback_query decorator to register a CallbackQueryHandler.
.. code-block:: python
from pyrogram import Client
app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
@app.on_callback_query()
def answer(client, callback_query):
callback_query.answer(f"Button contains: '{callback_query.data}'", show_alert=True)
app.run() # Automatically start() and idle()

View File

@ -0,0 +1,21 @@
echobot
=======
This simple echo bot replies to every private text message.
It uses the @on_message decorator to register a MessageHandler and applies two filters on it:
Filters.text and Filters.private to make sure it will reply to private text messages only.
.. code-block:: python
from pyrogram import Client, Filters
app = Client("my_account")
@app.on_message(Filters.text & Filters.private)
def echo(client, message):
message.reply(message.text)
app.run() # Automatically start() and idle()

View File

@ -0,0 +1,15 @@
get_chat_members
================
This example shows how to get all the members of a chat.
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
target = "pyrogramchat" # Target channel/supergroup
with app:
for member in app.iter_chat_members(target):
print(member.user.first_name)

View File

@ -0,0 +1,14 @@
get_dialogs
===========
This example shows how to get the full dialogs list (as user).
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
with app:
for dialog in app.iter_dialogs():
print(dialog.chat.title or dialog.chat.first_name)

View File

@ -0,0 +1,15 @@
get_history
===========
This example shows how to get the full message history of a chat, starting from the latest message.
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
target = "me" # "me" refers to your own chat (Saved Messages)
with app:
for message in app.iter_history(target):
print(message.text)

View File

@ -0,0 +1,21 @@
hello_world
===========
This example demonstrates a basic API usage
.. code-block:: python
from pyrogram import Client
# Create a new Client instance
app = Client("my_account")
with app:
# Send a message, Markdown is enabled by default
app.send_message("me", "Hi there! I'm using **Pyrogram**")
# Send a location
app.send_location("me", 51.500729, -0.124583)
# Send a sticker
app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE")

View File

@ -0,0 +1,46 @@
Examples
========
This page contains example scripts to show you how Pyrogram looks like.
Every script is working right away (provided you correctly set up your credentials), meaning you can simply copy-paste
and run. The only things you have to change are session names and target chats, where applicable.
The examples listed below can be treated as building blocks for your own applications and are meant to be simple enough
to give you a basic idea.
-----
.. csv-table::
:header: Example, Description
:widths: auto
:align: center
:doc:`hello_world`, "Demonstration of basic API usage"
:doc:`echobot`, "Echo every private text message"
:doc:`welcomebot`, "The Welcome Bot in @PyrogramChat"
:doc:`get_history`, "Get the full message history of a chat"
:doc:`get_chat_members`, "Get all the members of a chat"
:doc:`get_dialogs`, "Get all of your dialog chats"
:doc:`callback_queries`, "Handle callback queries (as bot) coming from inline button presses"
:doc:`inline_queries`, "Handle inline queries (as bot) and answer with results"
:doc:`use_inline_bots`, "Query an inline bot (as user) and send a result to a chat"
:doc:`bot_keyboards`, "Send normal and inline keyboards using regular bots"
:doc:`raw_updates`, "Handle raw updates (old, should be avoided)"
For more advanced examples, see https://snippets.pyrogram.org.
.. toctree::
:hidden:
hello_world
echobot
welcomebot
get_history
get_chat_members
get_dialogs
callback_queries
inline_queries
use_inline_bots
bot_keyboards
raw_updates

View File

@ -0,0 +1,61 @@
inline_queries
==============
This example shows how to handle inline queries.
Two results are generated when users invoke the bot inline mode, e.g.: @pyrogrambot hi.
It uses the @on_inline_query decorator to register an InlineQueryHandler.
.. code-block:: python
from pyrogram import (
Client, InlineQueryResultArticle, InputTextMessageContent, InlineKeyboardMarkup, InlineKeyboardButton
)
app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
@app.on_inline_query()
def answer(client, inline_query):
inline_query.answer(
results=[
InlineQueryResultArticle(
title="Installation",
input_message_content=InputTextMessageContent(
"Here's how to install **Pyrogram**"
),
url="https://docs.pyrogram.org/intro/install",
description="How to install Pyrogram",
thumb_url="https://i.imgur.com/JyxrStE.png",
reply_markup=InlineKeyboardMarkup(
[
[InlineKeyboardButton(
"Open website",
url="https://docs.pyrogram.org/intro/install"
)]
]
)
),
InlineQueryResultArticle(
title="Usage",
input_message_content=InputTextMessageContent(
"Here's how to use **Pyrogram**"
),
url="https://docs.pyrogram.org/start/invoking",
description="How to use Pyrogram",
thumb_url="https://i.imgur.com/JyxrStE.png",
reply_markup=InlineKeyboardMarkup(
[
[InlineKeyboardButton(
"Open website",
url="https://docs.pyrogram.org/start/invoking"
)]
]
)
)
],
cache_time=1
)
app.run() # Automatically start() and idle()

View File

@ -0,0 +1,18 @@
raw_updates
===========
This example shows how to handle raw updates.
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
@app.on_raw_update()
def raw(client, update, users, chats):
print(update)
app.run() # Automatically start() and idle()

View File

@ -0,0 +1,18 @@
use_inline_bots
===============
This example shows how to query an inline bot (as user).
.. code-block:: python
from pyrogram import Client
# Create a new Client
app = Client("my_account")
with app:
# Get bot results for "Fuzz Universe" from the inline bot @vid
bot_results = app.get_inline_bot_results("vid", "Fuzz Universe")
# Send the first result (bot_results.results[0]) to your own chat (Saved Messages)
app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id)

View File

@ -0,0 +1,33 @@
welcomebot
==========
This is the Welcome Bot in @PyrogramChat.
It uses the Emoji module to easily add emojis in your text messages and Filters
to make it only work for specific messages in a specific chat.
.. code-block:: python
from pyrogram import Client, Emoji, Filters
TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames
MENTION = "[{}](tg://user?id={})" # User mention markup
MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.org/)'s group chat {}!" # Welcome message
app = Client("my_account")
# Filter in only new_chat_members updates generated in TARGET chat
@app.on_message(Filters.chat(TARGET) & Filters.new_chat_members)
def welcome(client, message):
# Build the new members list (with mentions) by using their first_name
new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members]
# Build the welcome message by using an emoji and the list we built above
text = MESSAGE.format(Emoji.SPARKLES, ", ".join(new_members))
# Send the welcome message, without the web page preview
message.reply(text, disable_web_page_preview=True)
app.run() # Automatically start() and idle()

View File

@ -6,6 +6,7 @@ account; we are now aiming towards the core of the library. It's time to start p
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@ -13,7 +14,8 @@ account; we are now aiming towards the core of the library. It's time to start p
Basic Usage
-----------
Making API method calls with Pyrogram is very simple. Here's an example we are going to examine:
Making API method calls with Pyrogram is very simple. Here's a basic example we are going to examine step by step and
then expand to explain what happens underneath:
.. code-block:: python
@ -21,16 +23,13 @@ Making API method calls with Pyrogram is very simple. Here's an example we are g
app = Client("my_account")
app.start()
with app:
app.send_message("me", "Hi!")
print(app.get_me())
app.send_message("me", "Hi, it's me!")
app.send_location("me", 51.500729, -0.124583)
app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI")
Basic step-by-step
^^^^^^^^^^^^^^^^^^
app.stop()
#. Let's begin by importing the Client class from the Pyrogram package:
#. Let's begin by importing the Client class:
.. code-block:: python
@ -42,35 +41,26 @@ Making API method calls with Pyrogram is very simple. Here's an example we are g
app = Client("my_account")
#. To actually make use of any method, the client has to be started first:
#. The ``with`` context manager is a shortcut for starting, executing and stopping the Client:
.. code-block:: python
app.start()
with app:
#. Now, you can call any method you like:
.. code-block:: python
print(app.get_me()) # Print information about yourself
# Send messages to yourself:
app.send_message("me", "Hi!") # Text message
app.send_location("me", 51.500729, -0.124583) # Location
app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") # Sticker
#. Finally, when done, simply stop the client:
.. code-block:: python
app.stop()
app.send_message("me", "Hi!")
Context Manager
---------------
You can also use Pyrogram's Client in a context manager with the ``with`` statement. The client will automatically
:meth:`~pyrogram.Client.start` and :meth:`~pyrogram.Client.stop` gracefully, even in case of unhandled exceptions in
your code. The example above can be therefore rewritten in a much nicer way:
The ``with`` statement starts a context manager, which is used as a shortcut to automatically call
:meth:`~pyrogram.Client.start` and :meth:`~pyrogram.Client.stop`, which are methods required for Pyrogram to work
properly. The context manager does also gracefully stop the client, even in case of unhandled exceptions in your code.
This is how Pyrogram looks without the context manager:
.. code-block:: python
@ -78,10 +68,53 @@ your code. The example above can be therefore rewritten in a much nicer way:
app = Client("my_account")
with app:
print(app.get_me())
app.send_message("me", "Hi there! I'm using **Pyrogram**")
app.send_location("me", 51.500729, -0.124583)
app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI")
app.start()
app.send_message("me", "Hi!")
app.stop()
More examples can be found on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_.
Asynchronous Calls
------------------
In case you want Pyrogram to run asynchronously (e.g.: if you are using third party libraries that require you to call
them with ``await``), use the asynchronous context manager:
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
async def main():
async with app:
await app.send_message("me", "Hi!")
app.run(main())
Asynchronous step-by-step
^^^^^^^^^^^^^^^^^^^^^^^^^
#. Import the Client class and create an instance:
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
#. Async methods can't normally be executed at the top level, because they must be inside an async-defined function;
here we define one and put our code inside; the context manager is also being used differently in asyncio and
method calls require the await keyword:
.. code-block:: python
async def main():
async with app:
await app.send_message("me", "Hi!")
#. Finally, we tell Python to schedule our ``main()`` async function, which in turn will execute Pyrogram's code. Using
:meth:`~pyrogram.Client.run` this way is a friendly alternative for the much more verbose
``asyncio.get_event_loop().run_until_complete(main())``:
.. code-block:: python
app.run(main())

View File

@ -6,6 +6,7 @@ This page deals with updates and how to handle such events in Pyrogram. Let's ha
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@ -24,72 +25,14 @@ function will be called back by the framework and its body executed.
Registering a Handler
---------------------
To explain how handlers work let's have a look at the most used one, the :class:`~pyrogram.MessageHandler`, which will
be in charge for handling :class:`~pyrogram.Message` updates coming from all around your chats. Every other handler shares
the same setup logic; you should not have troubles settings them up once you learn from this section.
Using add_handler()
-------------------
The :meth:`~pyrogram.Client.add_handler` method takes any handler instance that wraps around your defined callback
function and registers it in your Client. Here's a full example that prints out the content of a message as soon as it
arrives:
.. code-block:: python
from pyrogram import Client, MessageHandler
def my_function(client, message):
print(message)
app = Client("my_account")
my_handler = MessageHandler(my_function)
app.add_handler(my_handler)
app.run()
Let's examine these four new pieces.
#. A callback function we defined which accepts two arguments -
*(client, message)*. This will be the function that gets executed every time a new message arrives and Pyrogram will
call that function by passing the client instance and the new message instance as argument.
.. code-block:: python
def my_function(client, message):
print(message)
#. The :class:`~pyrogram.MessageHandler`. This object tells Pyrogram the function we defined above must only handle
updates that are in form of a :class:`~pyrogram.Message`:
.. code-block:: python
my_handler = MessageHandler(my_function)
#. The method :meth:`~pyrogram.Client.add_handler`. This method is used to actually register the handler and let
Pyrogram know it needs to be taken into consideration when new updates arrive and the internal dispatching phase
begins.
.. code-block:: python
app.add_handler(my_handler)
#. The :meth:`~pyrogram.Client.run` method. What this does is simply call :meth:`~pyrogram.Client.start` and
a special method :meth:`~pyrogram.Client.idle` that keeps your main scripts alive until you press ``CTRL+C``; the
client will be automatically stopped after that.
.. code-block:: python
app.run()
To explain how handlers work let's examine the one which will be in charge for handling :class:`~pyrogram.types.Message`
updates coming from all around your chats. Every other handler shares the same setup logic; you should not have
troubles settings them up once you learn from this section.
Using Decorators
----------------
^^^^^^^^^^^^^^^^
All of the above will become quite verbose, especially in case you have lots of handlers to register. A much nicer way
to do so is by decorating your callback function with the :meth:`~pyrogram.Client.on_message` decorator.
The most elegant way to register a message handler is by using the :meth:`~pyrogram.Client.on_message` decorator:
.. code-block:: python
@ -100,7 +43,58 @@ to do so is by decorating your callback function with the :meth:`~pyrogram.Clien
@app.on_message()
def my_handler(client, message):
print(message)
message.forward("me")
app.run()
The defined function ``my_handler``, which accepts the two arguments *(client, message)*, will be the function that gets
executed every time a new message arrives.
Asynchronous handlers
^^^^^^^^^^^^^^^^^^^^^
You can also have asynchronous handlers; you only need to define the callback function using ``async def`` and call API
methods by placing ``await`` in front of them:
.. code-block:: python
@app.on_message()
async def my_handler(client, message):
await message.forward("me")
.. note::
You can mix ``def`` and ``async def`` handlers as much as you need, Pyrogram will still work concurrently and
efficiently regardless of what you choose.
Using add_handler()
^^^^^^^^^^^^^^^^^^^
The :meth:`~pyrogram.Client.add_handler` method takes any handler instance that wraps around your defined callback
function and registers it in your Client. It us useful in case you want to programmatically add handlers (or in case,
for some reason, you don't like to use decorators).
.. code-block:: python
from pyrogram import Client
from pyrogram.handlers import MessageHandler
def my_function(client, message):
message.forward("me")
app = Client("my_account")
my_handler = MessageHandler(my_function)
app.add_handler(my_handler)
app.run()
The same about asynchronous handlers applies for :meth:`~pyrogram.Client.add_handler`:
.. code-block:: python
async def my_function(client, message):
await message.forward("me")

View File

@ -10,6 +10,7 @@ Telegram API with its functions and types.
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@ -18,7 +19,7 @@ Telegram Raw API
----------------
If you can't find a high-level method for your needs or if you want complete, low-level access to the whole
Telegram API, you have to use the raw :mod:`~pyrogram.api.functions` and :mod:`~pyrogram.api.types`.
Telegram API, you have to use the raw :mod:`~pyrogram.raw.functions` and :mod:`~pyrogram.raw.types`.
As already hinted, raw functions and types can be really confusing, mainly because people don't realize soon enough they
accept *only* the right types and that all required parameters must be filled in. This section will therefore explain
@ -41,7 +42,7 @@ Unlike the :doc:`methods <../api/methods/index>` found in Pyrogram's API, which
functions to be invoked from the raw Telegram API have a different way of usage and are more complex.
First of all, both :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>`
live in their respective packages (and sub-packages): ``pyrogram.api.functions``, ``pyrogram.api.types``. They all exist
live in their respective packages (and sub-packages): ``pyrogram.raw.functions``, ``pyrogram.raw.types``. They all exist
as Python classes, meaning you need to create an instance of each every time you need them and fill them in with the
correct values using named arguments.
@ -55,7 +56,7 @@ Here's some examples:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions
from pyrogram.raw import functions
with Client("my_account") as app:
app.send(
@ -70,7 +71,7 @@ Here's some examples:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions, types
from pyrogram.raw import functions, types
with Client("my_account") as app:
app.send(
@ -85,7 +86,7 @@ Here's some examples:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions, types
from pyrogram.raw import functions, types
with Client("my_account") as app:
app.send(
@ -109,9 +110,9 @@ sending messages with IDs only thanks to cached access hashes.
There are three different InputPeer types, one for each kind of Telegram entity.
Whenever an InputPeer is needed you must pass one of these:
- :class:`~pyrogram.api.types.InputPeerUser` - Users
- :class:`~pyrogram.api.types.InputPeerChat` - Basic Chats
- :class:`~pyrogram.api.types.InputPeerChannel` - Either Channels or Supergroups
- :class:`~pyrogram.raw.types.InputPeerUser` - Users
- :class:`~pyrogram.raw.types.InputPeerChat` - Basic Chats
- :class:`~pyrogram.raw.types.InputPeerChannel` - Either Channels or Supergroups
But you don't necessarily have to manually instantiate each object because, luckily for you, Pyrogram already provides
:meth:`~pyrogram.Client.resolve_peer` as a convenience utility method that returns the correct InputPeer

View File

@ -5,6 +5,7 @@ Users can interact with other bots via plain text messages as well as inline que
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -6,6 +6,7 @@ This page explains how this file is structured, how to use it and why.
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -1,16 +1,12 @@
Creating Filters
================
Pyrogram already provides lots of built-in :class:`~pyrogram.Filters` to work with, but in case you can't find
a specific one for your needs or want to build a custom filter by yourself (to be used in a different kind of handler,
for example) you can use :meth:`~pyrogram.Filters.create`.
.. note::
At the moment, the built-in filters are intended to be used with the :class:`~pyrogram.MessageHandler` only.
Pyrogram already provides lots of built-in :class:`~pyrogram.filters` to work with, but in case you can't find a
specific one for your needs or want to build a custom filter by yourself you can use :meth:`~pyrogram.filters.create`.
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@ -19,14 +15,14 @@ Custom Filters
--------------
An example to demonstrate how custom filters work is to show how to create and use one for the
:class:`~pyrogram.CallbackQueryHandler`. Note that callback queries updates are only received by bots as result of a
user pressing an inline button attached to the bot's message; create and :doc:`authorize your bot <../start/auth>`,
:class:`~pyrogram.handlers.CallbackQueryHandler`. Note that callback queries updates are only received by bots as result
of a user pressing an inline button attached to the bot's message; create and :doc:`authorize your bot <../start/auth>`,
then send a message with an inline keyboard to yourself. This allows you to test your filter by pressing the inline
button:
.. code-block:: python
from pyrogram import InlineKeyboardMarkup, InlineKeyboardButton
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
app.send_message(
"username", # Change this to your username or id
@ -39,7 +35,7 @@ button:
Basic Filters
-------------
For this basic filter we will be using only the first parameter of :meth:`~pyrogram.Filters.create`.
For this basic filter we will be using only the first parameter of :meth:`~pyrogram.filters.create`.
The code below creates a simple filter for hardcoded, static callback data. This filter will only allow callback queries
containing "pyrogram" as data, that is, the function *func* you pass returns True in case the callback query data
@ -47,17 +43,21 @@ equals to ``"pyrogram"``.
.. code-block:: python
static_data_filter = Filters.create(lambda _, query: query.data == "pyrogram")
from pyrogram import filters
static_data_filter = filters.create(lambda _, query: query.data == "pyrogram")
The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example, the same
could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter scope:
could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter's scope:
.. code-block:: python
from pyrogram import filters
def func(_, query):
return query.data == "pyrogram"
static_data_filter = Filters.create(func)
static_data_filter = filters.create(func)
The filter usage remains the same:
@ -71,14 +71,16 @@ Filters with Arguments
----------------------
A much cooler filter would be one that accepts "pyrogram" or any other data as argument at usage time.
A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.Filters.create` method.
A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.filters.create` method.
This is how a dynamic custom filter looks like:
.. code-block:: python
from pyrogram import filters
def dynamic_data_filter(data):
return Filters.create(
return filters.create(
lambda flt, query: flt.data == query.data,
data=data # "data" kwarg is accessed with "flt.data" above
)

View File

@ -6,6 +6,7 @@ to actually worry about -- that's normal -- and luckily for you, Pyrogram provid
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -6,6 +6,7 @@ Here we'll show some advanced usages when working with :doc:`update handlers <..
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@ -25,21 +26,21 @@ For example, take these two handlers:
.. code-block:: python
:emphasize-lines: 1, 6
@app.on_message(Filters.text | Filters.sticker)
@app.on_message(filters.text | filters.sticker)
def text_or_sticker(client, message):
print("Text or Sticker")
@app.on_message(Filters.text)
@app.on_message(filters.text)
def just_text(client, message):
print("Just Text")
Here, ``just_text`` is never executed because ``text_or_sticker``, which has been registered first, already handles
texts (``Filters.text`` is shared and conflicting). To enable it, register the handler using a different group:
texts (``filters.text`` is shared and conflicting). To enable it, register the handler using a different group:
.. code-block:: python
@app.on_message(Filters.text, group=1)
@app.on_message(filters.text, group=1)
def just_text(client, message):
print("Just Text")
@ -47,7 +48,7 @@ Or, if you want ``just_text`` to be executed *before* ``text_or_sticker`` (note
.. code-block:: python
@app.on_message(Filters.text, group=-1)
@app.on_message(filters.text, group=-1)
def just_text(client, message):
print("Just Text")
@ -55,7 +56,7 @@ With :meth:`~pyrogram.Client.add_handler` (without decorators) the same can be a
.. code-block:: python
app.add_handler(MessageHandler(just_text, Filters.text), -1)
app.add_handler(MessageHandler(just_text, filters.text), -1)
Update propagation
------------------
@ -67,17 +68,17 @@ continue to propagate the same update to the next groups until all the handlers
.. code-block:: python
@app.on_message(Filters.private)
@app.on_message(filters.private)
def _(client, message):
print(0)
@app.on_message(Filters.private, group=1)
@app.on_message(filters.private, group=1)
def _(client, message):
raise Exception("Unhandled exception!") # Simulate an unhandled exception
@app.on_message(Filters.private, group=2)
@app.on_message(filters.private, group=2)
def _(client, message):
print(2)
@ -109,18 +110,18 @@ Example with ``stop_propagation()``:
.. code-block:: python
@app.on_message(Filters.private)
@app.on_message(filters.private)
def _(client, message):
print(0)
@app.on_message(Filters.private, group=1)
@app.on_message(filters.private, group=1)
def _(client, message):
print(1)
message.stop_propagation()
@app.on_message(Filters.private, group=2)
@app.on_message(filters.private, group=2)
def _(client, message):
print(2)
@ -130,18 +131,18 @@ Example with ``raise StopPropagation``:
from pyrogram import StopPropagation
@app.on_message(Filters.private)
@app.on_message(filters.private)
def _(client, message):
print(0)
@app.on_message(Filters.private, group=1)
@app.on_message(filters.private, group=1)
def _(client, message):
print(1)
raise StopPropagation
@app.on_message(Filters.private, group=2)
@app.on_message(filters.private, group=2)
def _(client, message):
print(2)
@ -177,19 +178,19 @@ Example with ``continue_propagation()``:
.. code-block:: python
@app.on_message(Filters.private)
@app.on_message(filters.private)
def _(client, message):
print(0)
message.continue_propagation()
@app.on_message(Filters.private)
@app.on_message(filters.private)
def _(client, message):
print(1)
message.continue_propagation()
@app.on_message(Filters.private)
@app.on_message(filters.private)
def _(client, message):
print(2)
@ -199,19 +200,19 @@ Example with ``raise ContinuePropagation``:
from pyrogram import ContinuePropagation
@app.on_message(Filters.private)
@app.on_message(filters.private)
def _(client, message):
print(0)
raise ContinuePropagation
@app.on_message(Filters.private)
@app.on_message(filters.private)
def _(client, message):
print(1)
raise ContinuePropagation
@app.on_message(Filters.private)
@app.on_message(filters.private)
def _(client, message):
print(2)

View File

@ -8,6 +8,7 @@ actually is the MTProto and the Bot API.
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -6,6 +6,7 @@ through an intermediate SOCKS5 proxy server.
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -10,6 +10,7 @@ visit and learn from each library documentation.
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@ -69,8 +70,7 @@ Using ``apscheduler``
scheduler.start()
app.run()
``apscheduler`` does also support async code, here's an example with
`Pyrogram Asyncio <https://docs.pyrogram.org/intro/install.html#asynchronous>`_:
``apscheduler`` does also support async code, here's an example:
.. code-block:: python

View File

@ -7,6 +7,7 @@ humans and another more compact for machines that is able to recover the origina
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -22,6 +22,7 @@ That's how a session looks like on the Android app, showing the three main piece
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -11,6 +11,7 @@ different Pyrogram applications with **minimal boilerplate code**.
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@ -51,7 +52,8 @@ after importing your modules, like this:
.. code-block:: python
from pyrogram import Client, MessageHandler, Filters
from pyrogram import Client, filters
from pyrogram.handlers import MessageHandler
from handlers import echo, echo_reversed
@ -60,19 +62,19 @@ after importing your modules, like this:
app.add_handler(
MessageHandler(
echo,
Filters.text & Filters.private))
filters.text & filters.private))
app.add_handler(
MessageHandler(
echo_reversed,
Filters.text & Filters.private),
filters.text & filters.private),
group=1)
app.run()
This is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to
manually ``import``, manually :meth:`~pyrogram.Client.add_handler` and manually instantiate each
:class:`~pyrogram.MessageHandler` object because **you can't use those cool decorators** for your
:class:`~pyrogram.handlers.MessageHandler` object because **you can't use those cool decorators** for your
functions. So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically.
Using Smart Plugins
@ -102,15 +104,15 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight
.. code-block:: python
:emphasize-lines: 4, 9
from pyrogram import Client, Filters
from pyrogram import Client, filters
@Client.on_message(Filters.text & Filters.private)
@Client.on_message(filters.text & filters.private)
def echo(client, message):
message.reply(message.text)
@Client.on_message(Filters.text & Filters.private, group=1)
@Client.on_message(filters.text & filters.private, group=1)
def echo_reversed(client, message):
message.reply(message.text[::-1])
@ -306,7 +308,7 @@ updates) will be modified in such a way that a special ``handler`` attribute poi
.. code-block:: python
:emphasize-lines: 5, 6
@Client.on_message(Filters.text & Filters.private)
@Client.on_message(filters.text & filters.private)
def echo(client, message):
message.reply(message.text)

View File

@ -7,6 +7,7 @@ decide to manually terminate it) and is used to authorize a client to execute AP
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -20,6 +20,7 @@ Telegram's test servers without hassle. All you need to do is start a new sessio
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -19,6 +19,7 @@ variety of decorations that can also be nested in order to combine multiple styl
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----

View File

@ -1,14 +1,15 @@
Using Filters
=============
So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time a specific update
comes from the server, but there's much more than that to come.
So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time an update comes
from the server, but there's much more than that to come.
Here we'll discuss about :class:`~pyrogram.Filters`. Filters enable a fine-grain control over what kind of
Here we'll discuss about :obj:`~pyrogram.filters`. Filters enable a fine-grain control over what kind of
updates are allowed or not to be passed in your callback functions, based on their inner details.
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@ -24,10 +25,10 @@ Let's start right away with a simple example:
.. code-block:: python
:emphasize-lines: 4
from pyrogram import Filters
from pyrogram import filters
@app.on_message(Filters.audio)
@app.on_message(filters.audio)
def my_handler(client, message):
print(message)
@ -35,16 +36,17 @@ Let's start right away with a simple example:
callback function itself:
.. code-block:: python
:emphasize-lines: 8
:emphasize-lines: 9
from pyrogram import Filters, MessageHandler
from pyrogram import filters
from pyrogram.handlers import MessageHandler
def my_handler(client, message):
print(message)
app.add_handler(MessageHandler(my_handler, Filters.audio))
app.add_handler(MessageHandler(my_handler, filters.audio))
Combining Filters
-----------------
@ -61,7 +63,7 @@ Here are some examples:
.. code-block:: python
@app.on_message(Filters.text & ~Filters.edited)
@app.on_message(filters.text & ~filters.edited)
def my_handler(client, message):
print(message)
@ -69,21 +71,21 @@ Here are some examples:
.. code-block:: python
@app.on_message(Filters.sticker & (Filters.channel | Filters.private))
@app.on_message(filters.sticker & (filters.channel | filters.private))
def my_handler(client, message):
print(message)
Advanced Filters
----------------
Some filters, like :meth:`~pyrogram.Filters.command` or :meth:`~pyrogram.Filters.regex`
Some filters, like :meth:`~pyrogram.filters.command` or :meth:`~pyrogram.filters.regex`
can also accept arguments:
- Message is either a */start* or */help* **command**.
.. code-block:: python
@app.on_message(Filters.command(["start", "help"]))
@app.on_message(filters.command(["start", "help"]))
def my_handler(client, message):
print(message)
@ -91,7 +93,7 @@ can also accept arguments:
.. code-block:: python
@app.on_message(Filters.regex("pyrogram"))
@app.on_message(filters.regex("pyrogram"))
def my_handler(client, message):
print(message)
@ -99,16 +101,16 @@ More handlers using different filters can also live together.
.. code-block:: python
@app.on_message(Filters.command("start"))
@app.on_message(filters.command("start"))
def start_command(client, message):
print("This is the /start command")
@app.on_message(Filters.command("help"))
@app.on_message(filters.command("help"))
def help_command(client, message):
print("This is the /help command")
@app.on_message(Filters.chat("PyrogramChat"))
@app.on_message(filters.chat("PyrogramChat"))
def from_pyrogramchat(client, message):
print("New message in @PyrogramChat")

View File

@ -1,121 +0,0 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View File

@ -1,23 +0,0 @@
# Examples
This folder contains example scripts to show you how **Pyrogram** looks like.
Every script is working right away (provided you correctly set up your credentials), meaning you can simply copy-paste
and run. The only things you have to change are session names and target chats.
All the examples listed in this directory are licensed under the terms of the [CC0 1.0 Universal](LICENSE) license and
can be freely used as basic building blocks for your own applications without worrying about copyrights.
Example | Description
---: | :---
[**hello_world**](hello_world.py) | Demonstration of basic API usage
[**echobot**](echobot.py) | Echo every private text message
[**welcomebot**](welcomebot.py) | The Welcome Bot in [@PyrogramChat](https://t.me/pyrogramchat)
[**get_history**](get_history.py) | Get the full message history of a chat
[**get_chat_members**](get_chat_members.py) | Get all the members of a chat
[**get_dialogs**](get_dialogs.py) | Get all of your dialog chats
[**callback_queries**](callback_queries.py) | Handle callback queries (as bot) coming from inline button presses
[**inline_queries**](inline_queries.py) | Handle inline queries (as bot) and answer with results
[**use_inline_bots**](use_inline_bots.py) | Query an inline bot (as user) and send a result to a chat
[**bot_keyboards**](bot_keyboards.py) | Send normal and inline keyboards using regular bots
[**raw_updates**](raw_updates.py) | Handle raw updates (old, should be avoided)

View File

@ -1,57 +0,0 @@
"""This example will show you how to send normal and inline keyboards (as bot).
You must log-in as a regular bot in order to send keyboards (use the token from @BotFather).
Any attempt in sending keyboards with a user account will be simply ignored by the server.
send_message() is used as example, but a keyboard can be sent with any other send_* methods,
like send_audio(), send_document(), send_location(), etc...
"""
from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
# Create a client using your bot token
app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
with app:
app.send_message(
"haskell", # Edit this
"This is a ReplyKeyboardMarkup example",
reply_markup=ReplyKeyboardMarkup(
[
["A", "B", "C", "D"], # First row
["E", "F", "G"], # Second row
["H", "I"], # Third row
["J"] # Fourth row
],
resize_keyboard=True # Make the keyboard smaller
)
)
app.send_message(
"haskell", # Edit this
"This is a InlineKeyboardMarkup example",
reply_markup=InlineKeyboardMarkup(
[
[ # First row
InlineKeyboardButton( # Generates a callback query when pressed
"Button",
callback_data=b"data" # Note how callback_data must be bytes
),
InlineKeyboardButton( # Opens a web URL
"URL",
url="https://docs.pyrogram.org"
),
],
[ # Second row
InlineKeyboardButton( # Opens the inline interface
"Choose chat",
switch_inline_query="pyrogram"
),
InlineKeyboardButton( # Opens the inline interface in the current chat
"Inline here",
switch_inline_query_current_chat="pyrogram"
)
]
]
)
)

View File

@ -1,16 +0,0 @@
"""This example shows how to handle callback queries, i.e.: queries coming from inline button presses.
It uses the @on_callback_query decorator to register a CallbackQueryHandler.
"""
from pyrogram import Client
app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
@app.on_callback_query()
def answer(client, callback_query):
callback_query.answer("Button contains: '{}'".format(callback_query.data), show_alert=True)
app.run() # Automatically start() and idle()

View File

@ -1,17 +0,0 @@
"""This simple echo bot replies to every private text message.
It uses the @on_message decorator to register a MessageHandler and applies two filters on it:
Filters.text and Filters.private to make sure it will reply to private text messages only.
"""
from pyrogram import Client, Filters
app = Client("my_account")
@app.on_message(Filters.text & Filters.private)
def echo(client, message):
message.reply(message.text)
app.run() # Automatically start() and idle()

View File

@ -1,10 +0,0 @@
"""This example shows how to get all the members of a chat."""
from pyrogram import Client
app = Client("my_account")
target = "pyrogramchat" # Target channel/supergroup
with app:
for member in app.iter_chat_members(target):
print(member.user.first_name)

View File

@ -1,9 +0,0 @@
"""This example shows how to get the full dialogs list (as user)."""
from pyrogram import Client
app = Client("my_account")
with app:
for dialog in app.iter_dialogs():
print(dialog.chat.title or dialog.chat.first_name)

View File

@ -1,10 +0,0 @@
"""This example shows how to get the full message history of a chat, starting from the latest message"""
from pyrogram import Client
app = Client("my_account")
target = "me" # "me" refers to your own chat (Saved Messages)
with app:
for message in app.iter_history(target):
print(message.text)

View File

@ -1,16 +0,0 @@
"""This example demonstrates a basic API usage"""
from pyrogram import Client
# Create a new Client instance
app = Client("my_account")
with app:
# Send a message, Markdown is enabled by default
app.send_message("me", "Hi there! I'm using **Pyrogram**")
# Send a location
app.send_location("me", 51.500729, -0.124583)
# Send a sticker
app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE")

View File

@ -1,54 +0,0 @@
"""This example shows how to handle inline queries.
Two results are generated when users invoke the bot inline mode, e.g.: @pyrogrambot hi.
It uses the @on_inline_query decorator to register an InlineQueryHandler.
"""
from uuid import uuid4
from pyrogram import (
Client, InlineQueryResultArticle, InputTextMessageContent, InlineKeyboardMarkup, InlineKeyboardButton
)
app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
@app.on_inline_query()
def answer(client, inline_query):
inline_query.answer(
results=[
InlineQueryResultArticle(
id=uuid4(),
title="Installation",
input_message_content=InputTextMessageContent(
"Here's how to install **Pyrogram**"
),
url="https://docs.pyrogram.org/intro/install",
description="How to install Pyrogram",
thumb_url="https://i.imgur.com/JyxrStE.png",
reply_markup=InlineKeyboardMarkup(
[
[InlineKeyboardButton("Open website", url="https://docs.pyrogram.org/intro/install")]
]
)
),
InlineQueryResultArticle(
id=uuid4(),
title="Usage",
input_message_content=InputTextMessageContent(
"Here's how to use **Pyrogram**"
),
url="https://docs.pyrogram.org/start/invoking",
description="How to use Pyrogram",
thumb_url="https://i.imgur.com/JyxrStE.png",
reply_markup=InlineKeyboardMarkup(
[
[InlineKeyboardButton("Open website", url="https://docs.pyrogram.org/start/invoking")]
]
)
)
],
cache_time=1
)
app.run() # Automatically start() and idle()

View File

@ -1,13 +0,0 @@
"""This example shows how to handle raw updates"""
from pyrogram import Client
app = Client("my_account")
@app.on_raw_update()
def raw(client, update, users, chats):
print(update)
app.run() # Automatically start() and idle()

View File

@ -1,13 +0,0 @@
"""This example shows how to query an inline bot (as user)"""
from pyrogram import Client
# Create a new Client
app = Client("my_account")
with app:
# Get bot results for "Fuzz Universe" from the inline bot @vid
bot_results = app.get_inline_bot_results("vid", "Fuzz Universe")
# Send the first result (bot_results.results[0]) to your own chat (Saved Messages)
app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id)

View File

@ -1,29 +0,0 @@
"""This is the Welcome Bot in @PyrogramChat.
It uses the Emoji module to easily add emojis in your text messages and Filters
to make it only work for specific messages in a specific chat.
"""
from pyrogram import Client, Emoji, Filters
TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames
MENTION = "[{}](tg://user?id={})" # User mention markup
MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.org/)'s group chat {}!" # Welcome message
app = Client("my_account")
# Filter in only new_chat_members updates generated in TARGET chat
@app.on_message(Filters.chat(TARGET) & Filters.new_chat_members)
def welcome(client, message):
# Build the new members list (with mentions) by using their first_name
new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members]
# Build the welcome message by using an emoji and the list we built above
text = MESSAGE.format(Emoji.SPARKLES, ", ".join(new_members))
# Send the welcome message, without the web page preview
message.reply(text, disable_web_page_preview=True)
app.run() # Automatically start() and idle()

View File

@ -16,10 +16,23 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
__version__ = "0.18.0-async"
__version__ = "1.0.0b1"
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
__copyright__ = "Copyright (C) 2017-2020 Dan <https://github.com/delivrance>"
from .client import *
from .client.handlers import *
from .client.types import *
class StopTransmission(StopAsyncIteration):
pass
class StopPropagation(StopAsyncIteration):
pass
class ContinuePropagation(StopAsyncIteration):
pass
from . import types, filters, handlers, emoji
from .client import Client
from .sync import idle

1069
pyrogram/client.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2020 Dan <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .client import Client
from .ext import BaseClient, Emoji
from .filters import Filters
__all__ = [
"Client", "BaseClient", "Emoji", "Filters",
]

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2020 Dan <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .base_client import BaseClient
from .dispatcher import Dispatcher
from .emoji import Emoji
from .file_data import FileData
from .link import Link
from .syncer import Syncer

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2020 Dan <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
class FileData:
def __init__(
self, *, media_type: int = None, dc_id: int = None, document_id: int = None, access_hash: int = None,
thumb_size: str = None, peer_id: int = None, peer_type: str = None, peer_access_hash: int = None,
volume_id: int = None, local_id: int = None, is_big: bool = None, file_size: int = None, mime_type: str = None,
file_name: str = None, date: int = None, file_ref: str = None
):
self.media_type = media_type
self.dc_id = dc_id
self.document_id = document_id
self.access_hash = access_hash
self.thumb_size = thumb_size
self.peer_id = peer_id
self.peer_type = peer_type
self.peer_access_hash = peer_access_hash
self.volume_id = volume_id
self.local_id = local_id
self.is_big = is_big
self.file_size = file_size
self.mime_type = mime_type
self.file_name = file_name
self.date = date
self.file_ref = file_ref

View File

@ -1,52 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2020 Dan <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import html
class Link(str):
HTML = "<a href={url}>{text}</a>"
MD = "[{text}]({url})"
def __init__(self, url: str, text: str, style: str):
super().__init__()
self.url = url
self.text = text
self.style = style
@staticmethod
def format(url: str, text: str, style: str):
if style in ["md", "markdown"]:
fmt = Link.MD
elif style in ["combined", "html", None]:
fmt = Link.HTML
else:
raise ValueError("{} is not a valid style/parse mode".format(style))
return fmt.format(url=url, text=html.escape(text))
# noinspection PyArgumentList
def __new__(cls, url, text, style):
return str.__new__(cls, Link.format(url, text, style))
def __call__(self, other: str = None, *, style: str = None):
return Link.format(self.url, other or self.text, style or self.style)
def __str__(self):
return Link.format(self.url, self.text, self.style)

View File

@ -1,19 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2020 Dan <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .filters import Filters

View File

@ -1,84 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2020 Dan <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import asyncio
class Filter:
def __call__(self, message):
raise NotImplementedError
def __invert__(self):
return InvertFilter(self)
def __and__(self, other):
return AndFilter(self, other)
def __or__(self, other):
return OrFilter(self, other)
class InvertFilter(Filter):
def __init__(self, base):
self.base = base
async def __call__(self, message):
if asyncio.iscoroutinefunction(self.base.__call__):
x = await self.base(message)
else:
x = self.base(message)
return not x
class AndFilter(Filter):
def __init__(self, base, other):
self.base = base
self.other = other
async def __call__(self, message):
if asyncio.iscoroutinefunction(self.base.__call__):
x = await self.base(message)
else:
x = self.base(message)
if asyncio.iscoroutinefunction(self.other.__call__):
y = await self.other(message)
else:
y = self.other(message)
return x and y
class OrFilter(Filter):
def __init__(self, base, other):
self.base = base
self.other = other
async def __call__(self, message):
if asyncio.iscoroutinefunction(self.base.__call__):
x = await self.base(message)
else:
x = self.base(message)
if asyncio.iscoroutinefunction(self.other.__call__):
y = await self.other(message)
else:
y = self.other(message)
return x or y

View File

@ -1,404 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2020 Dan <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import re
from typing import Callable, Union
from .filter import Filter
from ..types import Message, CallbackQuery, InlineQuery
from ..types.bots_and_keyboards import InlineKeyboardMarkup, ReplyKeyboardMarkup
CUSTOM_FILTER_NAME = "CustomFilter"
def create(func: Callable, name: str = None, **kwargs) -> Filter:
"""Easily create a custom filter.
Custom filters give you extra control over which updates are allowed or not to be processed by your handlers.
Parameters:
func (``callable``):
A function that accepts two positional arguments *(filter, update)* and returns a boolean: True if the
update should be handled, False otherwise. The *filter* argument refers to the filter itself and can be used
to access keyword arguments (read below). The *update* argument type will vary depending on which
`Handler <handlers>`_ is coming from. For example, in a :obj:`MessageHandler` the *update* argument will be
a :obj:`Message`; in a :obj:`CallbackQueryHandler` the *update* will be a :obj:`CallbackQuery`. Your
function body can then access the incoming update attributes and decide whether to allow it or not.
name (``str``, *optional*):
Your filter's name. Can be anything you like.
Defaults to "CustomFilter".
**kwargs (``any``, *optional*):
Any keyword argument you would like to pass. Useful when creating parameterized custom filters, such as
:meth:`~Filters.command` or :meth:`~Filters.regex`.
"""
# TODO: unpack kwargs using **kwargs into the dict itself. For Python 3.5+ only
d = {"__call__": func}
d.update(kwargs)
return type(name or CUSTOM_FILTER_NAME, (Filter,), d)()
class Filters:
"""This class provides access to all library-defined Filters available in Pyrogram.
The Filters listed here are currently intended to be used with the :obj:`MessageHandler` only.
At the moment, if you want to filter updates coming from different `Handlers <handlers.html>`_ you have to create
your own filters with :meth:`~Filters.create` and use them in the same way.
"""
create = create
all = create(lambda _, m: True, "AllFilter")
"""Filter all messages."""
me = create(lambda _, m: bool(m.from_user and m.from_user.is_self), "MeFilter")
"""Filter messages generated by you yourself."""
bot = create(lambda _, m: bool(m.from_user and m.from_user.is_bot), "BotFilter")
"""Filter messages coming from bots."""
incoming = create(lambda _, m: not m.outgoing, "IncomingFilter")
"""Filter incoming messages. Messages sent to your own chat (Saved Messages) are also recognised as incoming."""
outgoing = create(lambda _, m: m.outgoing, "OutgoingFilter")
"""Filter outgoing messages. Messages sent to your own chat (Saved Messages) are not recognized as outgoing."""
text = create(lambda _, m: bool(m.text), "TextFilter")
"""Filter text messages."""
reply = create(lambda _, m: bool(m.reply_to_message), "ReplyFilter")
"""Filter messages that are replies to other messages."""
forwarded = create(lambda _, m: bool(m.forward_date), "ForwardedFilter")
"""Filter messages that are forwarded."""
caption = create(lambda _, m: bool(m.caption), "CaptionFilter")
"""Filter media messages that contain captions."""
edited = create(lambda _, m: bool(m.edit_date), "EditedFilter")
"""Filter edited messages."""
audio = create(lambda _, m: bool(m.audio), "AudioFilter")
"""Filter messages that contain :obj:`Audio` objects."""
document = create(lambda _, m: bool(m.document), "DocumentFilter")
"""Filter messages that contain :obj:`Document` objects."""
photo = create(lambda _, m: bool(m.photo), "PhotoFilter")
"""Filter messages that contain :obj:`Photo` objects."""
sticker = create(lambda _, m: bool(m.sticker), "StickerFilter")
"""Filter messages that contain :obj:`Sticker` objects."""
animation = create(lambda _, m: bool(m.animation), "AnimationFilter")
"""Filter messages that contain :obj:`Animation` objects."""
game = create(lambda _, m: bool(m.game), "GameFilter")
"""Filter messages that contain :obj:`Game` objects."""
video = create(lambda _, m: bool(m.video), "VideoFilter")
"""Filter messages that contain :obj:`Video` objects."""
media_group = create(lambda _, m: bool(m.media_group_id), "MediaGroupFilter")
"""Filter messages containing photos or videos being part of an album."""
voice = create(lambda _, m: bool(m.voice), "VoiceFilter")
"""Filter messages that contain :obj:`Voice` note objects."""
video_note = create(lambda _, m: bool(m.video_note), "VideoNoteFilter")
"""Filter messages that contain :obj:`VideoNote` objects."""
contact = create(lambda _, m: bool(m.contact), "ContactFilter")
"""Filter messages that contain :obj:`Contact` objects."""
location = create(lambda _, m: bool(m.location), "LocationFilter")
"""Filter messages that contain :obj:`Location` objects."""
venue = create(lambda _, m: bool(m.venue), "VenueFilter")
"""Filter messages that contain :obj:`Venue` objects."""
web_page = create(lambda _, m: m.web_page, "WebPageFilter")
"""Filter messages sent with a webpage preview."""
poll = create(lambda _, m: m.poll, "PollFilter")
"""Filter messages that contain :obj:`Poll` objects."""
private = create(lambda _, m: bool(m.chat and m.chat.type in {"private", "bot"}), "PrivateFilter")
"""Filter messages sent in private chats."""
group = create(lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"}), "GroupFilter")
"""Filter messages sent in group or supergroup chats."""
channel = create(lambda _, m: bool(m.chat and m.chat.type == "channel"), "ChannelFilter")
"""Filter messages sent in channels."""
new_chat_members = create(lambda _, m: bool(m.new_chat_members), "NewChatMembersFilter")
"""Filter service messages for new chat members."""
left_chat_member = create(lambda _, m: bool(m.left_chat_member), "LeftChatMemberFilter")
"""Filter service messages for members that left the chat."""
new_chat_title = create(lambda _, m: bool(m.new_chat_title), "NewChatTitleFilter")
"""Filter service messages for new chat titles."""
new_chat_photo = create(lambda _, m: bool(m.new_chat_photo), "NewChatPhotoFilter")
"""Filter service messages for new chat photos."""
delete_chat_photo = create(lambda _, m: bool(m.delete_chat_photo), "DeleteChatPhotoFilter")
"""Filter service messages for deleted photos."""
group_chat_created = create(lambda _, m: bool(m.group_chat_created), "GroupChatCreatedFilter")
"""Filter service messages for group chat creations."""
supergroup_chat_created = create(lambda _, m: bool(m.supergroup_chat_created), "SupergroupChatCreatedFilter")
"""Filter service messages for supergroup chat creations."""
channel_chat_created = create(lambda _, m: bool(m.channel_chat_created), "ChannelChatCreatedFilter")
"""Filter service messages for channel chat creations."""
migrate_to_chat_id = create(lambda _, m: bool(m.migrate_to_chat_id), "MigrateToChatIdFilter")
"""Filter service messages that contain migrate_to_chat_id."""
migrate_from_chat_id = create(lambda _, m: bool(m.migrate_from_chat_id), "MigrateFromChatIdFilter")
"""Filter service messages that contain migrate_from_chat_id."""
pinned_message = create(lambda _, m: bool(m.pinned_message), "PinnedMessageFilter")
"""Filter service messages for pinned messages."""
game_high_score = create(lambda _, m: bool(m.game_high_score), "GameHighScoreFilter")
"""Filter service messages for game high scores."""
reply_keyboard = create(lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup), "ReplyKeyboardFilter")
"""Filter messages containing reply keyboard markups"""
inline_keyboard = create(lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup), "InlineKeyboardFilter")
"""Filter messages containing inline keyboard markups"""
mentioned = create(lambda _, m: bool(m.mentioned), "MentionedFilter")
"""Filter messages containing mentions"""
via_bot = create(lambda _, m: bool(m.via_bot), "ViaBotFilter")
"""Filter messages sent via inline bots"""
service = create(lambda _, m: bool(m.service), "ServiceFilter")
"""Filter service messages.
A service message contains any of the following fields set: *left_chat_member*,
*new_chat_title*, *new_chat_photo*, *delete_chat_photo*, *group_chat_created*, *supergroup_chat_created*,
*channel_chat_created*, *migrate_to_chat_id*, *migrate_from_chat_id*, *pinned_message*, *game_score*.
"""
media = create(lambda _, m: bool(m.media), "MediaFilter")
"""Filter media messages.
A media message contains any of the following fields set: *audio*, *document*, *photo*, *sticker*, *video*,
*animation*, *voice*, *video_note*, *contact*, *location*, *venue*, *poll*.
"""
scheduled = create(lambda _, m: bool(m.scheduled), "ScheduledFilter")
"""Filter messages that have been scheduled (not yet sent)."""
from_scheduled = create(lambda _, m: bool(m.from_scheduled), "FromScheduledFilter")
"""Filter new automatically sent messages that were previously scheduled."""
# Messages from linked channels are forwarded automatically by Telegram and have no sender (from_user is None).
linked_channel = create(lambda _, m: bool(m.forward_from_chat and not m.from_user), "LinkedChannelFilter")
"""Filter messages that are automatically forwarded from the linked channel to the group chat."""
@staticmethod
def command(
commands: str or list,
prefixes: str or list = "/",
case_sensitive: bool = False
):
"""Filter commands, i.e.: text messages starting with "/" or any other custom prefix.
Parameters:
commands (``str`` | ``list``):
The command or list of commands as string the filter should look for.
Examples: "start", ["start", "help", "settings"]. When a message text containing
a command arrives, the command itself and its arguments will be stored in the *command*
field of the :obj:`Message`.
prefixes (``str`` | ``list``, *optional*):
A prefix or a list of prefixes as string the filter should look for.
Defaults to "/" (slash). Examples: ".", "!", ["/", "!", "."], list(".:!").
Pass None or "" (empty string) to allow commands with no prefix at all.
case_sensitive (``bool``, *optional*):
Pass True if you want your command(s) to be case sensitive. Defaults to False.
Examples: when True, command="Start" would trigger /Start but not /start.
"""
command_re = re.compile(r"([\"'])(.*?)(?<!\\)\1|(\S+)")
def func(flt, message):
text = message.text or message.caption
message.command = None
if not text:
return False
pattern = r"^{}(?:\s|$)" if flt.case_sensitive else r"(?i)^{}(?:\s|$)"
for prefix in flt.prefixes:
if not text.startswith(prefix):
continue
without_prefix = text[len(prefix):]
for cmd in flt.commands:
if not re.match(pattern.format(re.escape(cmd)), without_prefix):
continue
# match.groups are 1-indexed, group(1) is the quote, group(2) is the text
# between the quotes, group(3) is unquoted, whitespace-split text
# Remove the escape character from the arguments
message.command = [cmd] + [
re.sub(r"\\([\"'])", r"\1", m.group(2) or m.group(3) or "")
for m in command_re.finditer(without_prefix[len(cmd):])
]
return True
return False
commands = commands if isinstance(commands, list) else [commands]
commands = {c if case_sensitive else c.lower() for c in commands}
prefixes = [] if prefixes is None else prefixes
prefixes = prefixes if isinstance(prefixes, list) else [prefixes]
prefixes = set(prefixes) if prefixes else {""}
return create(
func,
"CommandFilter",
commands=commands,
prefixes=prefixes,
case_sensitive=case_sensitive
)
@staticmethod
def regex(pattern: Union[str, re.Pattern], flags: int = 0):
"""Filter updates that match a given regular expression pattern.
Can be applied to handlers that receive one of the following updates:
- :obj:`Message`: The filter will match ``text`` or ``caption``.
- :obj:`CallbackQuery`: The filter will match ``data``.
- :obj:`InlineQuery`: The filter will match ``query``.
When a pattern matches, all the `Match Objects <https://docs.python.org/3/library/re.html#match-objects>`_ are
stored in the ``matches`` field of the update object itself.
Parameters:
pattern (``str`` | ``Pattern``):
The regex pattern as string or as pre-compiled pattern.
flags (``int``, *optional*):
Regex flags.
"""
def func(flt, update):
if isinstance(update, Message):
value = update.text or update.caption
elif isinstance(update, CallbackQuery):
value = update.data
elif isinstance(update, InlineQuery):
value = update.query
else:
raise ValueError("Regex filter doesn't work with {}".format(type(update)))
if value:
update.matches = list(flt.p.finditer(value)) or None
return bool(update.matches)
return create(
func,
"RegexFilter",
p=pattern if isinstance(pattern, re.Pattern) else re.compile(pattern, flags)
)
# noinspection PyPep8Naming
class user(Filter, set):
"""Filter messages coming from one or more users.
You can use `set bound methods <https://docs.python.org/3/library/stdtypes.html#set>`_ to manipulate the
users container.
Parameters:
users (``int`` | ``str`` | ``list``):
Pass one or more user ids/usernames to filter users.
For you yourself, "me" or "self" can be used as well.
Defaults to None (no users).
"""
def __init__(self, users: int or str or list = None):
users = [] if users is None else users if isinstance(users, list) else [users]
super().__init__(
"me" if u in ["me", "self"]
else u.lower().strip("@") if isinstance(u, str)
else u for u in users
)
def __call__(self, message):
return (message.from_user
and (message.from_user.id in self
or (message.from_user.username
and message.from_user.username.lower() in self)
or ("me" in self
and message.from_user.is_self)))
# noinspection PyPep8Naming
class chat(Filter, set):
"""Filter messages coming from one or more chats.
You can use `set bound methods <https://docs.python.org/3/library/stdtypes.html#set>`_ to manipulate the
chats container.
Parameters:
chats (``int`` | ``str`` | ``list``):
Pass one or more chat ids/usernames to filter chats.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
Defaults to None (no chats).
"""
def __init__(self, chats: int or str or list = None):
chats = [] if chats is None else chats if isinstance(chats, list) else [chats]
super().__init__(
"me" if c in ["me", "self"]
else c.lower().strip("@") if isinstance(c, str)
else c for c in chats
)
def __call__(self, message):
return (message.chat
and (message.chat.id in self
or (message.chat.username
and message.chat.username.lower() in self)
or ("me" in self
and message.from_user
and message.from_user.is_self
and not message.outgoing)))
dan = create(lambda _, m: bool(m.from_user and m.from_user.id == 23122162), "DanFilter")

View File

@ -1,104 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2020 Dan <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import hashlib
import os
from pyrogram.api import types
def btoi(b: bytes) -> int:
return int.from_bytes(b, "big")
def itob(i: int) -> bytes:
return i.to_bytes(256, "big")
def sha256(data: bytes) -> bytes:
return hashlib.sha256(data).digest()
def xor(a: bytes, b: bytes) -> bytes:
return bytes(i ^ j for i, j in zip(a, b))
def compute_hash(algo: types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow, password: str) -> bytes:
hash1 = sha256(algo.salt1 + password.encode() + algo.salt1)
hash2 = sha256(algo.salt2 + hash1 + algo.salt2)
hash3 = hashlib.pbkdf2_hmac("sha512", hash2, algo.salt1, 100000)
return sha256(algo.salt2 + hash3 + algo.salt2)
# noinspection PyPep8Naming
def compute_check(r: types.account.Password, password: str) -> types.InputCheckPasswordSRP:
algo = r.current_algo
p_bytes = algo.p
p = btoi(algo.p)
g_bytes = itob(algo.g)
g = algo.g
B_bytes = r.srp_B
B = btoi(B_bytes)
srp_id = r.srp_id
x_bytes = compute_hash(algo, password)
x = btoi(x_bytes)
g_x = pow(g, x, p)
k_bytes = sha256(p_bytes + g_bytes)
k = btoi(k_bytes)
kg_x = (k * g_x) % p
while True:
a_bytes = os.urandom(256)
a = btoi(a_bytes)
A = pow(g, a, p)
A_bytes = itob(A)
u = btoi(sha256(A_bytes + B_bytes))
if u > 0:
break
g_b = (B - kg_x) % p
ux = u * x
a_ux = a + ux
S = pow(g_b, a_ux, p)
S_bytes = itob(S)
K_bytes = sha256(S_bytes)
M1_bytes = sha256(
xor(sha256(p_bytes), sha256(g_bytes))
+ sha256(algo.salt1)
+ sha256(algo.salt2)
+ A_bytes
+ B_bytes
+ K_bytes
)
return types.InputCheckPasswordSRP(srp_id=srp_id, A=A_bytes, M1=M1_bytes)

View File

@ -1,71 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2020 Dan <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from uuid import uuid4
from ..bots_and_keyboards import InlineKeyboardMarkup
from ..input_message_content import InputMessageContent
from ..object import Object
"""- :obj:`InlineQueryResultCachedAudio`
- :obj:`InlineQueryResultCachedDocument`
- :obj:`InlineQueryResultCachedGif`
- :obj:`InlineQueryResultCachedMpeg4Gif`
- :obj:`InlineQueryResultCachedPhoto`
- :obj:`InlineQueryResultCachedSticker`
- :obj:`InlineQueryResultCachedVideo`
- :obj:`InlineQueryResultCachedVoice`
- :obj:`InlineQueryResultAudio`
- :obj:`InlineQueryResultContact`
- :obj:`InlineQueryResultGame`
- :obj:`InlineQueryResultDocument`
- :obj:`InlineQueryResultGif`
- :obj:`InlineQueryResultLocation`
- :obj:`InlineQueryResultMpeg4Gif`
- :obj:`InlineQueryResultPhoto`
- :obj:`InlineQueryResultVenue`
- :obj:`InlineQueryResultVideo`
- :obj:`InlineQueryResultVoice`"""
class InlineQueryResult(Object):
"""One result of an inline query.
Pyrogram currently supports results of the following types:
- :obj:`InlineQueryResultArticle`
- :obj:`InlineQueryResultPhoto`
- :obj:`InlineQueryResultAnimation`
"""
def __init__(
self,
type: str,
id: str,
input_message_content: InputMessageContent,
reply_markup: InlineKeyboardMarkup
):
super().__init__()
self.type = type
self.id = str(uuid4()) if id is None else str(id)
self.input_message_content = input_message_content
self.reply_markup = reply_markup
async def write(self):
pass

View File

@ -68,7 +68,7 @@ class TCP:
password=proxy.get("password", None)
)
log.info("Using proxy {}:{}".format(hostname, port))
log.info(f"Using proxy {hostname}:{port}")
else:
self.socket = socks.socksocket(
socket.AF_INET6 if ipv6

View File

@ -19,8 +19,8 @@
import logging
import os
from pyrogram.crypto import aes
from .tcp import TCP
from ....crypto.aes import AES
log = logging.getLogger(__name__)
@ -49,7 +49,7 @@ class TCPAbridgedO(TCP):
self.encrypt = (nonce[8:40], nonce[40:56], bytearray(1))
self.decrypt = (temp[0:32], temp[32:48], bytearray(1))
nonce[56:64] = AES.ctr256_encrypt(nonce, *self.encrypt)[56:64]
nonce[56:64] = aes.ctr256_encrypt(nonce, *self.encrypt)[56:64]
await super().send(nonce)
@ -57,7 +57,7 @@ class TCPAbridgedO(TCP):
length = len(data) // 4
await super().send(
AES.ctr256_encrypt(
aes.ctr256_encrypt(
(bytes([length])
if length <= 126
else b"\x7f" + length.to_bytes(3, "little"))
@ -72,7 +72,7 @@ class TCPAbridgedO(TCP):
if length is None:
return None
length = AES.ctr256_decrypt(length, *self.decrypt)
length = aes.ctr256_decrypt(length, *self.decrypt)
if length == b"\x7f":
length = await super().recv(3)
@ -80,11 +80,11 @@ class TCPAbridgedO(TCP):
if length is None:
return None
length = AES.ctr256_decrypt(length, *self.decrypt)
length = aes.ctr256_decrypt(length, *self.decrypt)
data = await super().recv(int.from_bytes(length, "little") * 4)
if data is None:
return None
return AES.ctr256_decrypt(data, *self.decrypt)
return aes.ctr256_decrypt(data, *self.decrypt)

View File

@ -20,8 +20,8 @@ import logging
import os
from struct import pack, unpack
from pyrogram.crypto import aes
from .tcp import TCP
from ....crypto.aes import AES
log = logging.getLogger(__name__)
@ -50,13 +50,13 @@ class TCPIntermediateO(TCP):
self.encrypt = (nonce[8:40], nonce[40:56], bytearray(1))
self.decrypt = (temp[0:32], temp[32:48], bytearray(1))
nonce[56:64] = AES.ctr256_encrypt(nonce, *self.encrypt)[56:64]
nonce[56:64] = aes.ctr256_encrypt(nonce, *self.encrypt)[56:64]
await super().send(nonce)
async def send(self, data: bytes, *args):
await super().send(
AES.ctr256_encrypt(
aes.ctr256_encrypt(
pack("<i", len(data)) + data,
*self.encrypt
)
@ -68,11 +68,11 @@ class TCPIntermediateO(TCP):
if length is None:
return None
length = AES.ctr256_decrypt(length, *self.decrypt)
length = aes.ctr256_decrypt(length, *self.decrypt)
data = await super().recv(unpack("<i", length)[0])
if data is None:
return None
return AES.ctr256_decrypt(data, *self.decrypt)
return aes.ctr256_decrypt(data, *self.decrypt)

View File

@ -15,9 +15,3 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .aes import AES
from .kdf import KDF
from .mtproto import MTProto
from .prime import Prime
from .rsa import RSA

View File

@ -26,30 +26,28 @@ try:
log.info("Using TgCrypto")
class AES:
@classmethod
def ige256_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
return tgcrypto.ige256_encrypt(data, key, iv)
def ige256_encrypt(data: bytes, key: bytes, iv: bytes) -> bytes:
return tgcrypto.ige256_encrypt(data, key, iv)
@classmethod
def ige256_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
return tgcrypto.ige256_decrypt(data, key, iv)
@staticmethod
def ctr256_encrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
return tgcrypto.ctr256_encrypt(data, key, iv, state or bytearray(1))
def ige256_decrypt(data: bytes, key: bytes, iv: bytes) -> bytes:
return tgcrypto.ige256_decrypt(data, key, iv)
@staticmethod
def ctr256_decrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
return tgcrypto.ctr256_decrypt(data, key, iv, state or bytearray(1))
@staticmethod
def xor(a: bytes, b: bytes) -> bytes:
return int.to_bytes(
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
len(a),
"big",
)
def ctr256_encrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
return tgcrypto.ctr256_encrypt(data, key, iv, state or bytearray(1))
def ctr256_decrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
return tgcrypto.ctr256_decrypt(data, key, iv, state or bytearray(1))
def xor(a: bytes, b: bytes) -> bytes:
return int.to_bytes(
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
len(a),
"big",
)
except ImportError:
import pyaes
@ -60,75 +58,73 @@ except ImportError:
)
class AES:
@classmethod
def ige256_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
return cls.ige(data, key, iv, True)
def ige256_encrypt(data: bytes, key: bytes, iv: bytes) -> bytes:
return ige(data, key, iv, True)
@classmethod
def ige256_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
return cls.ige(data, key, iv, False)
@classmethod
def ctr256_encrypt(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
return cls.ctr(data, key, iv, state or bytearray(1))
def ige256_decrypt(data: bytes, key: bytes, iv: bytes) -> bytes:
return ige(data, key, iv, False)
@classmethod
def ctr256_decrypt(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
return cls.ctr(data, key, iv, state or bytearray(1))
@staticmethod
def xor(a: bytes, b: bytes) -> bytes:
return int.to_bytes(
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
len(a),
"big",
)
def ctr256_encrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
return ctr(data, key, iv, state or bytearray(1))
@classmethod
def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
cipher = pyaes.AES(key)
iv_1 = iv[:16]
iv_2 = iv[16:]
def ctr256_decrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
return ctr(data, key, iv, state or bytearray(1))
data = [data[i: i + 16] for i in range(0, len(data), 16)]
if encrypt:
for i, chunk in enumerate(data):
iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2)
iv_2 = chunk
else:
for i, chunk in enumerate(data):
iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1)
iv_1 = chunk
def xor(a: bytes, b: bytes) -> bytes:
return int.to_bytes(
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
len(a),
"big",
)
return b"".join(data)
@classmethod
def ctr(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray) -> bytes:
cipher = pyaes.AES(key)
def ige(data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
cipher = pyaes.AES(key)
out = bytearray(data)
chunk = cipher.encrypt(iv)
iv_1 = iv[:16]
iv_2 = iv[16:]
for i in range(0, len(data), 16):
for j in range(0, min(len(data) - i, 16)):
out[i + j] ^= chunk[state[0]]
data = [data[i: i + 16] for i in range(0, len(data), 16)]
state[0] += 1
if encrypt:
for i, chunk in enumerate(data):
iv_1 = data[i] = xor(cipher.encrypt(xor(chunk, iv_1)), iv_2)
iv_2 = chunk
else:
for i, chunk in enumerate(data):
iv_2 = data[i] = xor(cipher.decrypt(xor(chunk, iv_2)), iv_1)
iv_1 = chunk
if state[0] >= 16:
state[0] = 0
return b"".join(data)
if state[0] == 0:
for k in range(15, -1, -1):
try:
iv[k] += 1
break
except ValueError:
iv[k] = 0
chunk = cipher.encrypt(iv)
def ctr(data: bytes, key: bytes, iv: bytearray, state: bytearray) -> bytes:
cipher = pyaes.AES(key)
return out
out = bytearray(data)
chunk = cipher.encrypt(iv)
for i in range(0, len(data), 16):
for j in range(0, min(len(data) - i, 16)):
out[i + j] ^= chunk[state[0]]
state[0] += 1
if state[0] >= 16:
state[0] = 0
if state[0] == 0:
for k in range(15, -1, -1):
try:
iv[k] += 1
break
except ValueError:
iv[k] = 0
chunk = cipher.encrypt(iv)
return out

View File

@ -20,43 +20,54 @@ from hashlib import sha256
from io import BytesIO
from os import urandom
from pyrogram.api.core import Message, Long
from . import AES, KDF
from pyrogram.raw.core import Message, Long
from . import aes
class MTProto:
@staticmethod
def pack(message: Message, salt: int, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> bytes:
data = Long(salt) + session_id + message.write()
padding = urandom(-(len(data) + 12) % 16 + 12)
def kdf(auth_key: bytes, msg_key: bytes, outgoing: bool) -> tuple:
# https://core.telegram.org/mtproto/description#defining-aes-key-and-initialization-vector
x = 0 if outgoing else 8
# 88 = 88 + 0 (outgoing message)
msg_key_large = sha256(auth_key[88: 88 + 32] + data + padding).digest()
msg_key = msg_key_large[8:24]
aes_key, aes_iv = KDF(auth_key, msg_key, True)
sha256_a = sha256(msg_key + auth_key[x: x + 36]).digest()
sha256_b = sha256(auth_key[x + 40:x + 76] + msg_key).digest() # 76 = 40 + 36
return auth_key_id + msg_key + AES.ige256_encrypt(data + padding, aes_key, aes_iv)
aes_key = sha256_a[:8] + sha256_b[8:24] + sha256_a[24:32]
aes_iv = sha256_b[:8] + sha256_a[8:24] + sha256_b[24:32]
@staticmethod
def unpack(b: BytesIO, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> Message:
assert b.read(8) == auth_key_id, b.getvalue()
return aes_key, aes_iv
msg_key = b.read(16)
aes_key, aes_iv = KDF(auth_key, msg_key, False)
data = BytesIO(AES.ige256_decrypt(b.read(), aes_key, aes_iv))
data.read(8)
# https://core.telegram.org/mtproto/security_guidelines#checking-session-id
assert data.read(8) == session_id
def pack(message: Message, salt: int, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> bytes:
data = Long(salt) + session_id + message.write()
padding = urandom(-(len(data) + 12) % 16 + 12)
message = Message.read(data)
# 88 = 88 + 0 (outgoing message)
msg_key_large = sha256(auth_key[88: 88 + 32] + data + padding).digest()
msg_key = msg_key_large[8:24]
aes_key, aes_iv = kdf(auth_key, msg_key, True)
# https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key
# https://core.telegram.org/mtproto/security_guidelines#checking-message-length
# 96 = 88 + 8 (incoming message)
assert msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24]
return auth_key_id + msg_key + aes.ige256_encrypt(data + padding, aes_key, aes_iv)
# https://core.telegram.org/mtproto/security_guidelines#checking-msg-id
assert message.msg_id % 2 != 0
return message
def unpack(b: BytesIO, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> Message:
assert b.read(8) == auth_key_id, b.getvalue()
msg_key = b.read(16)
aes_key, aes_iv = kdf(auth_key, msg_key, False)
data = BytesIO(aes.ige256_decrypt(b.read(), aes_key, aes_iv))
data.read(8)
# https://core.telegram.org/mtproto/security_guidelines#checking-session-id
assert data.read(8) == session_id
message = Message.read(data)
# https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key
# https://core.telegram.org/mtproto/security_guidelines#checking-message-length
# 96 = 88 + 8 (incoming message)
assert msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24]
# https://core.telegram.org/mtproto/security_guidelines#checking-msg-id
assert message.msg_id % 2 != 0
return message

Some files were not shown because too many files have changed in this diff Show More