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:
parent
2f0a1f4119
commit
538f1e3972
7
.gitignore
vendored
7
.gitignore
vendored
@ -6,9 +6,10 @@ unknown_errors.txt
|
|||||||
|
|
||||||
# Pyrogram generated code
|
# Pyrogram generated code
|
||||||
pyrogram/errors/exceptions/
|
pyrogram/errors/exceptions/
|
||||||
pyrogram/api/functions/
|
pyrogram/raw/functions/
|
||||||
pyrogram/api/types/
|
pyrogram/raw/types/
|
||||||
pyrogram/api/all.py
|
pyrogram/raw/base/
|
||||||
|
pyrogram/raw/all.py
|
||||||
|
|
||||||
# PyCharm stuff
|
# PyCharm stuff
|
||||||
.idea/
|
.idea/
|
||||||
|
@ -5,6 +5,6 @@ recursive-include pyrogram mime.types schema.sql
|
|||||||
|
|
||||||
## Exclude
|
## Exclude
|
||||||
prune pyrogram/errors/exceptions
|
prune pyrogram/errors/exceptions
|
||||||
prune pyrogram/api/functions
|
prune pyrogram/raw/functions
|
||||||
prune pyrogram/api/types
|
prune pyrogram/raw/types
|
||||||
exclude pyrogram/api/all.py
|
exclude pyrogram/raw/all.py
|
38
README.md
38
README.md
@ -21,25 +21,22 @@
|
|||||||
## Pyrogram
|
## Pyrogram
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from pyrogram import Client, Filters
|
from pyrogram import Client, filters
|
||||||
|
|
||||||
app = Client("my_account")
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.private)
|
@app.on_message(filters.private)
|
||||||
def hello(client, message):
|
async def hello(client, message):
|
||||||
message.reply_text("Hello {}".format(message.from_user.first_name))
|
await message.reply_text(f"Hello {message.from_user.mention}")
|
||||||
|
|
||||||
|
|
||||||
app.run()
|
app.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
**Pyrogram** is an elegant, easy-to-use [Telegram](https://telegram.org/) client library and framework written from the
|
**Pyrogram** is a modern, elegant and easy-to-use [Telegram](https://telegram.org/) framework written from the ground up
|
||||||
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).
|
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)
|
|
||||||
|
|
||||||
### Features
|
### 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.
|
- **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
|
- **Fast**: Crypto parts are boosted up by [TgCrypto](https://github.com/pyrogram/tgcrypto), a high-performance library
|
||||||
written in pure C.
|
written in pure C.
|
||||||
- **Documented**: Pyrogram API methods, types and public interfaces are well documented.
|
- **Asynchronous**: Allows both synchronous and asynchronous usages to fit all usage needs.
|
||||||
- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
|
- **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.
|
- **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.
|
- **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.
|
- **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
|
### Requirements
|
||||||
|
|
||||||
- Python 3.5.3 or higher.
|
- Python 3.6 or higher.
|
||||||
- A [Telegram API key](https://docs.pyrogram.org/intro/setup#api-keys).
|
- A [Telegram API key](https://docs.pyrogram.org/intro/setup#api-keys).
|
||||||
|
|
||||||
### Installing
|
### Installing
|
||||||
@ -67,17 +65,9 @@ pip3 install pyrogram
|
|||||||
|
|
||||||
### Resources
|
### Resources
|
||||||
|
|
||||||
- The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.org.
|
- The docs contain lots of resources to help you get 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
|
- Seeking extra help? Come join and ask our community: https://t.me/pyrogram.
|
||||||
for learning how Pyrogram works.
|
- For other kind of inquiries, you can send a [message](https://t.me/haskell) or an [e-mail](mailto:dan@pyrogram.org).
|
||||||
- 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!
|
|
||||||
|
|
||||||
### Copyright & License
|
### Copyright & License
|
||||||
|
|
||||||
|
@ -19,142 +19,110 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
from functools import partial
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import NamedTuple, List, Tuple
|
||||||
|
|
||||||
HOME = "compiler/api"
|
# from autoflake import fix_code
|
||||||
DESTINATION = "pyrogram/api"
|
# from black import format_str, FileMode
|
||||||
|
|
||||||
|
HOME_PATH = Path("compiler/api")
|
||||||
|
DESTINATION_PATH = Path("pyrogram/raw")
|
||||||
NOTICE_PATH = "NOTICE"
|
NOTICE_PATH = "NOTICE"
|
||||||
|
|
||||||
SECTION_RE = re.compile(r"---(\w+)---")
|
SECTION_RE = re.compile(r"---(\w+)---")
|
||||||
LAYER_RE = re.compile(r"//\sLAYER\s(\d+)")
|
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?!.<>#]+)")
|
ARGS_RE = re.compile(r"[^{](\w+):([\w?!.<>#]+)")
|
||||||
FLAGS_RE = re.compile(r"flags\.(\d+)\?")
|
FLAGS_RE = re.compile(r"flags\.(\d+)\?")
|
||||||
FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)")
|
FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)")
|
||||||
FLAGS_RE_3 = re.compile(r"flags:#")
|
FLAGS_RE_3 = re.compile(r"flags:#")
|
||||||
INT_RE = re.compile(r"int(\d+)")
|
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_constructors = {}
|
||||||
types_to_functions = {}
|
types_to_functions = {}
|
||||||
constructors_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):
|
class Combinator(NamedTuple):
|
||||||
if t in core_types:
|
section: str
|
||||||
if t == "long":
|
qualname: str
|
||||||
return "``int`` ``64-bit``"
|
namespace: str
|
||||||
elif "int" in t:
|
name: str
|
||||||
size = INT_RE.match(t)
|
id: str
|
||||||
return "``int`` ``{}-bit``".format(size.group(1)) if size else "``int`` ``32-bit``"
|
has_flags: bool
|
||||||
elif t == "double":
|
args: List[Tuple[str, str]]
|
||||||
return "``float`` ``64-bit``"
|
qualtype: str
|
||||||
elif t == "string":
|
typespace: str
|
||||||
return "``str``"
|
type: 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
|
|
||||||
|
|
||||||
|
|
||||||
def get_references(t: str):
|
def snake(s: str):
|
||||||
t = constructors_to_functions.get(t)
|
# https://stackoverflow.com/q/1175208
|
||||||
|
|
||||||
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
|
|
||||||
s = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", s)
|
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()
|
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("_")])
|
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):
|
def sort_args(args):
|
||||||
"""Put flags at the end"""
|
"""Put flags at the end"""
|
||||||
args = args.copy()
|
args = args.copy()
|
||||||
@ -163,99 +131,167 @@ def sort_args(args):
|
|||||||
for i in flags:
|
for i in flags:
|
||||||
args.remove(i)
|
args.remove(i)
|
||||||
|
|
||||||
|
try:
|
||||||
|
args.remove(("flags", "#"))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
return args + flags
|
return args + flags
|
||||||
|
|
||||||
|
|
||||||
def start():
|
def remove_whitespaces(source: str) -> str:
|
||||||
shutil.rmtree("{}/types".format(DESTINATION), ignore_errors=True)
|
"""Remove whitespaces from blank lines"""
|
||||||
shutil.rmtree("{}/functions".format(DESTINATION), ignore_errors=True)
|
lines = source.split("\n")
|
||||||
|
|
||||||
with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \
|
for i, _ in enumerate(lines):
|
||||||
open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \
|
if re.match(r"^\s+$", lines[i]):
|
||||||
open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api:
|
lines[i] = ""
|
||||||
schema = (auth.read() + system.read() + api.read()).splitlines()
|
|
||||||
|
|
||||||
with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f:
|
return "\n".join(lines)
|
||||||
mtproto_template = f.read()
|
|
||||||
|
|
||||||
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:
|
with open(NOTICE_PATH, encoding="utf-8") as f:
|
||||||
notice = []
|
notice = []
|
||||||
|
|
||||||
for line in f.readlines():
|
for line in f.readlines():
|
||||||
notice.append("# {}".format(line).strip())
|
notice.append(f"# {line}".strip())
|
||||||
|
|
||||||
notice = "\n".join(notice)
|
notice = "\n".join(notice)
|
||||||
|
|
||||||
section = None
|
section = None
|
||||||
layer = None
|
layer = None
|
||||||
namespaces = {"types": set(), "functions": set()}
|
|
||||||
combinators = []
|
combinators = []
|
||||||
|
|
||||||
for line in schema:
|
for line in schema:
|
||||||
# Check for section changer lines
|
# Check for section changer lines
|
||||||
s = SECTION_RE.match(line)
|
section_match = SECTION_RE.match(line)
|
||||||
if s:
|
if section_match:
|
||||||
section = s.group(1)
|
section = section_match.group(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Save the layer version
|
# Save the layer version
|
||||||
l = LAYER_RE.match(line)
|
layer_match = LAYER_RE.match(line)
|
||||||
if l:
|
if layer_match:
|
||||||
layer = l.group(1)
|
layer = layer_match.group(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
combinator = COMBINATOR_RE.match(line)
|
combinator_match = COMBINATOR_RE.match(line)
|
||||||
if combinator:
|
if combinator_match:
|
||||||
name, id, return_type, docs = combinator.groups()
|
# noinspection PyShadowingBuiltins
|
||||||
namespace, name = name.split(".") if "." in name else ("", name)
|
qualname, id, qualtype = combinator_match.groups()
|
||||||
args = ARGS_RE.findall(line.split(" //")[0])
|
|
||||||
|
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!
|
# Pingu!
|
||||||
has_flags = not not FLAGS_RE_3.findall(line)
|
has_flags = not not FLAGS_RE_3.findall(line)
|
||||||
|
|
||||||
# Fix file and folder name collision
|
args = ARGS_RE.findall(line)
|
||||||
if name == "updates":
|
|
||||||
name = "update"
|
|
||||||
|
|
||||||
# Fix arg name being "self" (reserved keyword)
|
# Fix arg name being "self" (reserved python keyword)
|
||||||
for i, item in enumerate(args):
|
for i, item in enumerate(args):
|
||||||
if item[0] == "self":
|
if item[0] == "self":
|
||||||
args[i] = ("is_self", item[1])
|
args[i] = ("is_self", item[1])
|
||||||
|
|
||||||
if namespace:
|
combinator = Combinator(
|
||||||
namespaces[section].add(namespace)
|
section=section,
|
||||||
|
qualname=qualname,
|
||||||
combinators.append(
|
namespace=namespace,
|
||||||
Combinator(
|
name=name,
|
||||||
section,
|
id=f"0x{id}",
|
||||||
namespace,
|
has_flags=has_flags,
|
||||||
capit(name),
|
args=args,
|
||||||
"0x{}".format(id.zfill(8)),
|
qualtype=qualtype,
|
||||||
args,
|
typespace=typespace,
|
||||||
has_flags,
|
type=type
|
||||||
".".join(
|
|
||||||
return_type.split(".")[:-1]
|
|
||||||
+ [capit(return_type.split(".")[-1])]
|
|
||||||
),
|
|
||||||
docs
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for c in combinators:
|
combinators.append(combinator)
|
||||||
return_type = c.return_type
|
|
||||||
|
|
||||||
if return_type.startswith("Vector"):
|
for c in combinators:
|
||||||
return_type = return_type.split("<")[1][:-1]
|
qualtype = c.qualtype
|
||||||
|
|
||||||
|
if qualtype.startswith("Vector"):
|
||||||
|
qualtype = qualtype.split("<")[1][:-1]
|
||||||
|
|
||||||
d = types_to_constructors if c.section == "types" else types_to_functions
|
d = types_to_constructors if c.section == "types" else types_to_functions
|
||||||
|
|
||||||
if return_type not in d:
|
if qualtype not in d:
|
||||||
d[return_type] = []
|
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 k, v in types_to_constructors.items():
|
||||||
for i in v:
|
for i in v:
|
||||||
@ -264,85 +300,100 @@ def start():
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
total = len(combinators)
|
# import json
|
||||||
current = 0
|
# print(json.dumps(namespaces_to_types, indent=2))
|
||||||
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
|
|
||||||
|
|
||||||
path = "{}/{}/{}".format(DESTINATION, c.section, c.namespace)
|
for qualtype in types_to_constructors:
|
||||||
os.makedirs(path, exist_ok=True)
|
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):
|
if module == "Updates":
|
||||||
with open(init, "w", encoding="utf-8") as f:
|
module = "UpdatesT"
|
||||||
f.write(notice + "\n\n")
|
|
||||||
|
|
||||||
with open(init, "a", encoding="utf-8") as f:
|
os.makedirs(dir_path, exist_ok=True)
|
||||||
f.write("from .{} import {}\n".format(snek(c.name), capit(c.name)))
|
|
||||||
|
|
||||||
|
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)
|
sorted_args = sort_args(c.args)
|
||||||
|
|
||||||
arguments = (
|
arguments = (
|
||||||
", "
|
(", *, " if c.args else "") +
|
||||||
+ ("*, " if c.args else "")
|
(", ".join(
|
||||||
+ (", ".join([get_argument_type(i) for i in sorted_args if i != ("flags", "#")]) if c.args else "")
|
[f"{i[0]}: {get_type_hint(i[1])}"
|
||||||
|
for i in sorted_args]
|
||||||
|
) if sorted_args else "")
|
||||||
)
|
)
|
||||||
|
|
||||||
fields = "\n ".join(
|
fields = "\n ".join(
|
||||||
["self.{0} = {0} # {1}".format(i[0], i[1]) for i in c.args if i != ("flags", "#")]
|
[f"self.{i[0]} = {i[0]} # {i[1]}"
|
||||||
) if c.args else "pass"
|
for i in sorted_args]
|
||||||
|
) if sorted_args else "pass"
|
||||||
|
|
||||||
|
docstring = ""
|
||||||
docstring_args = []
|
docstring_args = []
|
||||||
docs = c.docs.split("|")[1:] if c.docs else None
|
|
||||||
|
|
||||||
for i, arg in enumerate(sorted_args):
|
for i, arg in enumerate(sorted_args):
|
||||||
if arg == ("flags", "#"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
arg_name, arg_type = arg
|
arg_name, arg_type = arg
|
||||||
is_optional = FLAGS_RE.match(arg_type)
|
is_optional = FLAGS_RE.match(arg_type)
|
||||||
flag_number = is_optional.group(1) if is_optional else -1
|
flag_number = is_optional.group(1) if is_optional else -1
|
||||||
arg_type = arg_type.split("?")[-1]
|
arg_type = arg_type.split("?")[-1]
|
||||||
|
|
||||||
if docs:
|
docstring_args.append(
|
||||||
docstring_args.append(
|
"{}{}: {}".format(
|
||||||
"{} ({}{}):\n {}\n".format(
|
arg_name,
|
||||||
arg_name,
|
" (optional)".format(flag_number) if is_optional else "",
|
||||||
get_docstring_arg_type(arg_type, is_pyrogram_type=True),
|
get_docstring_arg_type(arg_type, is_pyrogram_type=c.namespace == "pyrogram")
|
||||||
", 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")
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
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:
|
if docstring_args:
|
||||||
docstring_args = "Parameters:\n " + "\n ".join(docstring_args)
|
docstring += " Parameters:\n " + "\n ".join(docstring_args)
|
||||||
else:
|
else:
|
||||||
docstring_args = "No parameters required."
|
docstring += " **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
|
|
||||||
|
|
||||||
if c.section == "functions":
|
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:
|
else:
|
||||||
references = get_references(".".join(filter(None, [c.namespace, c.name])))
|
references, count = get_references(c.qualname, "constructors")
|
||||||
|
|
||||||
if references:
|
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 "
|
write_types = read_types = "" if c.has_flags else "# No flags\n "
|
||||||
|
|
||||||
@ -355,17 +406,16 @@ def start():
|
|||||||
for i in c.args:
|
for i in c.args:
|
||||||
flag = FLAGS_RE.match(i[1])
|
flag = FLAGS_RE.match(i[1])
|
||||||
if flag:
|
if flag:
|
||||||
write_flags.append(
|
write_flags.append(f"flags |= (1 << {flag.group(1)}) if self.{i[0]} is not None else 0")
|
||||||
"flags |= (1 << {}) if self.{} is not None else 0".format(flag.group(1), i[0]))
|
|
||||||
|
|
||||||
write_flags = "\n ".join([
|
write_flags = "\n ".join([
|
||||||
"flags = 0",
|
"flags = 0",
|
||||||
"\n ".join(write_flags),
|
"\n ".join(write_flags),
|
||||||
"b.write(Int(flags))\n "
|
"data.write(Int(flags))\n "
|
||||||
])
|
])
|
||||||
|
|
||||||
write_types += write_flags
|
write_types += write_flags
|
||||||
read_types += "flags = Int.read(b)\n "
|
read_types += "flags = Int.read(data)\n "
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -374,126 +424,171 @@ def start():
|
|||||||
|
|
||||||
if flag_type == "true":
|
if flag_type == "true":
|
||||||
read_types += "\n "
|
read_types += "\n "
|
||||||
read_types += "{} = True if flags & (1 << {}) else False".format(arg_name, index)
|
read_types += f"{arg_name} = True if flags & (1 << {index}) else False"
|
||||||
elif flag_type in core_types:
|
elif flag_type in CORE_TYPES:
|
||||||
write_types += "\n "
|
write_types += "\n "
|
||||||
write_types += "if self.{} is not None:\n ".format(arg_name)
|
write_types += f"if self.{arg_name} is not None:\n "
|
||||||
write_types += "b.write({}(self.{}))\n ".format(flag_type.title(), arg_name)
|
write_types += f"data.write({flag_type.title()}(self.{arg_name}))\n "
|
||||||
|
|
||||||
read_types += "\n "
|
read_types += "\n "
|
||||||
read_types += "{} = {}.read(b) if flags & (1 << {}) else None".format(
|
read_types += f"{arg_name} = {flag_type.title()}.read(data) if flags & (1 << {index}) else None"
|
||||||
arg_name, flag_type.title(), index
|
|
||||||
)
|
|
||||||
elif "vector" in flag_type.lower():
|
elif "vector" in flag_type.lower():
|
||||||
sub_type = arg_type.split("<")[1][:-1]
|
sub_type = arg_type.split("<")[1][:-1]
|
||||||
|
|
||||||
write_types += "\n "
|
write_types += "\n "
|
||||||
write_types += "if self.{} is not None:\n ".format(arg_name)
|
write_types += f"if self.{arg_name} is not None:\n "
|
||||||
write_types += "b.write(Vector(self.{}{}))\n ".format(
|
write_types += "data.write(Vector(self.{}{}))\n ".format(
|
||||||
arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else ""
|
arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
read_types += "\n "
|
read_types += "\n "
|
||||||
read_types += "{} = TLObject.read(b{}) if flags & (1 << {}) else []\n ".format(
|
read_types += "{} = TLObject.read(data{}) if flags & (1 << {}) else []\n ".format(
|
||||||
arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else "", index
|
arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "", index
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
write_types += "\n "
|
write_types += "\n "
|
||||||
write_types += "if self.{} is not None:\n ".format(arg_name)
|
write_types += f"if self.{arg_name} is not None:\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 += "\n "
|
||||||
read_types += "{} = TLObject.read(b) if flags & (1 << {}) else None\n ".format(
|
read_types += f"{arg_name} = TLObject.read(data) if flags & (1 << {index}) else None\n "
|
||||||
arg_name, index
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
if arg_type in core_types:
|
if arg_type in CORE_TYPES:
|
||||||
write_types += "\n "
|
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 += "\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():
|
elif "vector" in arg_type.lower():
|
||||||
sub_type = arg_type.split("<")[1][:-1]
|
sub_type = arg_type.split("<")[1][:-1]
|
||||||
|
|
||||||
write_types += "\n "
|
write_types += "\n "
|
||||||
write_types += "b.write(Vector(self.{}{}))\n ".format(
|
write_types += "data.write(Vector(self.{}{}))\n ".format(
|
||||||
arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else ""
|
arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
read_types += "\n "
|
read_types += "\n "
|
||||||
read_types += "{} = TLObject.read(b{})\n ".format(
|
read_types += "{} = TLObject.read(data{})\n ".format(
|
||||||
arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else ""
|
arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else ""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
write_types += "\n "
|
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 += "\n "
|
||||||
read_types += "{} = TLObject.read(b)\n ".format(arg_name)
|
read_types += f"{arg_name} = TLObject.read(data)\n "
|
||||||
|
|
||||||
if c.docs:
|
slots = ", ".join([f'"{i[0]}"' for i in sorted_args])
|
||||||
description = c.docs.split("|")[0].split("§")[1]
|
return_arguments = ", ".join([f"{i[0]}={i[0]}" for i in sorted_args])
|
||||||
docstring_args = description + "\n\n " + docstring_args
|
|
||||||
|
|
||||||
with open("{}/{}.py".format(path, snek(c.name)), "w", encoding="utf-8") as f:
|
compiled_combinator = combinator_tmpl.format(
|
||||||
if c.docs:
|
notice=notice,
|
||||||
f.write(
|
warning=WARNING,
|
||||||
pyrogram_template.format(
|
name=c.name,
|
||||||
notice=notice,
|
docstring=docstring,
|
||||||
class_name=capit(c.name),
|
slots=slots,
|
||||||
docstring_args=docstring_args,
|
id=c.id,
|
||||||
object_id=c.id,
|
qualname=f"pyrogram.raw.{c.section}.{c.qualname}",
|
||||||
arguments=arguments,
|
arguments=arguments,
|
||||||
fields=fields
|
fields=fields,
|
||||||
)
|
read_types=read_types,
|
||||||
)
|
write_types=write_types,
|
||||||
else:
|
return_arguments=return_arguments
|
||||||
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)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
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(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 = {")
|
f.write("objects = {")
|
||||||
|
|
||||||
for c in combinators:
|
for c in combinators:
|
||||||
path = ".".join(filter(None, [c.section, c.namespace, capit(c.name)]))
|
f.write(f'\n {c.id}: "pyrogram.raw.{c.section}.{c.qualname}",')
|
||||||
f.write("\n {}: \"pyrogram.api.{}\",".format(c.id, path))
|
|
||||||
|
|
||||||
f.write("\n 0xbc799737: \"pyrogram.api.core.BoolFalse\",")
|
f.write('\n 0xbc799737: "pyrogram.raw.core.BoolFalse",')
|
||||||
f.write("\n 0x997275b5: \"pyrogram.api.core.BoolTrue\",")
|
f.write('\n 0x997275b5: "pyrogram.raw.core.BoolTrue",')
|
||||||
f.write("\n 0x1cb5c415: \"pyrogram.api.core.Vector\",")
|
f.write('\n 0x1cb5c415: "pyrogram.raw.core.Vector",')
|
||||||
f.write("\n 0x73f1f8dc: \"pyrogram.api.core.MsgContainer\",")
|
f.write('\n 0x73f1f8dc: "pyrogram.raw.core.MsgContainer",')
|
||||||
f.write("\n 0xae500895: \"pyrogram.api.core.FutureSalts\",")
|
f.write('\n 0xae500895: "pyrogram.raw.core.FutureSalts",')
|
||||||
f.write("\n 0x0949d9dc: \"pyrogram.api.core.FutureSalt\",")
|
f.write('\n 0x0949d9dc: "pyrogram.raw.core.FutureSalt",')
|
||||||
f.write("\n 0x3072cfa1: \"pyrogram.api.core.GzipPacked\",")
|
f.write('\n 0x3072cfa1: "pyrogram.raw.core.GzipPacked",')
|
||||||
f.write("\n 0x5bb8e511: \"pyrogram.api.core.Message\",")
|
f.write('\n 0x5bb8e511: "pyrogram.raw.core.Message",')
|
||||||
|
|
||||||
f.write("\n}\n")
|
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__:
|
if "__main__" == __name__:
|
||||||
HOME = "."
|
HOME_PATH = Path(".")
|
||||||
DESTINATION = "../../pyrogram/api"
|
DESTINATION_PATH = Path("../../pyrogram/raw")
|
||||||
NOTICE_PATH = "../../NOTICE"
|
NOTICE_PATH = Path("../../NOTICE")
|
||||||
start()
|
|
||||||
|
start(format=False)
|
||||||
|
@ -87,7 +87,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType;
|
|||||||
storage.fileWebp#1081464c = storage.FileType;
|
storage.fileWebp#1081464c = storage.FileType;
|
||||||
|
|
||||||
userEmpty#200250ba id:int = User;
|
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;
|
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
|
||||||
userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = 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;
|
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;
|
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;
|
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
|
||||||
chatParticipantCreator#da13538a user_id:int = ChatParticipant;
|
chatParticipantCreator#da13538a user_id:int = ChatParticipant;
|
||||||
@ -203,7 +203,7 @@ inputReportReasonOther#e1746d0a text:string = ReportReason;
|
|||||||
inputReportReasonCopyright#9b89f93a = ReportReason;
|
inputReportReasonCopyright#9b89f93a = ReportReason;
|
||||||
inputReportReasonGeoIrrelevant#dbd4feed = 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;
|
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;
|
inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall;
|
||||||
|
|
||||||
phoneCallEmpty#5366c915 id:long = PhoneCall;
|
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;
|
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.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = 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.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_b: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 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;
|
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.5?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?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;
|
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;
|
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.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;
|
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.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.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>;
|
||||||
photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;
|
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.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;
|
||||||
stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats;
|
stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats;
|
||||||
|
|
||||||
// LAYER 116
|
// LAYER 117
|
35
compiler/api/template/combinator.txt
Normal file
35
compiler/api/template/combinator.txt
Normal 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()
|
@ -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()
|
|
@ -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}
|
|
17
compiler/api/template/type.txt
Normal file
17
compiler/api/template/type.txt
Normal 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}"
|
@ -25,11 +25,13 @@ HOME = "compiler/docs"
|
|||||||
DESTINATION = "docs/source/telegram"
|
DESTINATION = "docs/source/telegram"
|
||||||
PYROGRAM_API_DEST = "docs/source/api"
|
PYROGRAM_API_DEST = "docs/source/api"
|
||||||
|
|
||||||
FUNCTIONS_PATH = "pyrogram/api/functions"
|
FUNCTIONS_PATH = "pyrogram/raw/functions"
|
||||||
TYPES_PATH = "pyrogram/api/types"
|
TYPES_PATH = "pyrogram/raw/types"
|
||||||
|
BASE_PATH = "pyrogram/raw/base"
|
||||||
|
|
||||||
FUNCTIONS_BASE = "functions"
|
FUNCTIONS_BASE = "functions"
|
||||||
TYPES_BASE = "types"
|
TYPES_BASE = "types"
|
||||||
|
BASE_BASE = "base"
|
||||||
|
|
||||||
|
|
||||||
def snek(s: str):
|
def snek(s: str):
|
||||||
@ -70,7 +72,7 @@ def generate(source_path, base):
|
|||||||
page_template.format(
|
page_template.format(
|
||||||
title=name,
|
title=name,
|
||||||
title_markup="=" * len(name),
|
title_markup="=" * len(name),
|
||||||
full_class_path="pyrogram.api.{}".format(
|
full_class_path="pyrogram.raw.{}".format(
|
||||||
".".join(full_path.split("/")[:-1]) + "." + name
|
".".join(full_path.split("/")[:-1]) + "." + name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -92,14 +94,14 @@ def generate(source_path, base):
|
|||||||
|
|
||||||
if k != base:
|
if k != base:
|
||||||
inner_path = base + "/" + k + "/index" + ".rst"
|
inner_path = base + "/" + k + "/index" + ".rst"
|
||||||
module = "pyrogram.api.{}.{}".format(base, k)
|
module = "pyrogram.raw.{}.{}".format(base, k)
|
||||||
else:
|
else:
|
||||||
for i in sorted(list(all_entities), reverse=True):
|
for i in sorted(list(all_entities), reverse=True):
|
||||||
if i != base:
|
if i != base:
|
||||||
entities.insert(0, "{0}/index".format(i))
|
entities.insert(0, "{0}/index".format(i))
|
||||||
|
|
||||||
inner_path = base + "/index" + ".rst"
|
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:
|
with open(DESTINATION + "/" + inner_path, "w", encoding="utf-8") as f:
|
||||||
if k == base:
|
if k == base:
|
||||||
@ -128,7 +130,6 @@ def pyrogram_api():
|
|||||||
utilities="""
|
utilities="""
|
||||||
Utilities
|
Utilities
|
||||||
start
|
start
|
||||||
idle
|
|
||||||
stop
|
stop
|
||||||
run
|
run
|
||||||
restart
|
restart
|
||||||
@ -264,6 +265,7 @@ def pyrogram_api():
|
|||||||
send_code
|
send_code
|
||||||
resend_code
|
resend_code
|
||||||
sign_in
|
sign_in
|
||||||
|
sign_in_bot
|
||||||
sign_up
|
sign_up
|
||||||
get_password_hint
|
get_password_hint
|
||||||
check_password
|
check_password
|
||||||
@ -302,6 +304,15 @@ def pyrogram_api():
|
|||||||
f2.write(title + "\n" + "=" * len(title) + "\n\n")
|
f2.write(title + "\n" + "=" * len(title) + "\n\n")
|
||||||
f2.write(".. automethod:: pyrogram.Client.{}()".format(method))
|
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))
|
f.write(template.format(**fmt_keys))
|
||||||
|
|
||||||
# Types
|
# Types
|
||||||
@ -405,7 +416,7 @@ def pyrogram_api():
|
|||||||
title = "{}".format(type)
|
title = "{}".format(type)
|
||||||
|
|
||||||
f2.write(title + "\n" + "=" * len(title) + "\n\n")
|
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))
|
f.write(template.format(**fmt_keys))
|
||||||
|
|
||||||
@ -506,7 +517,7 @@ def pyrogram_api():
|
|||||||
title = "{}()".format(bm)
|
title = "{}()".format(bm)
|
||||||
|
|
||||||
f2.write(title + "\n" + "=" * len(title) + "\n\n")
|
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))
|
f.write(template.format(**fmt_keys))
|
||||||
|
|
||||||
@ -525,12 +536,14 @@ def start():
|
|||||||
|
|
||||||
generate(TYPES_PATH, TYPES_BASE)
|
generate(TYPES_PATH, TYPES_BASE)
|
||||||
generate(FUNCTIONS_PATH, FUNCTIONS_BASE)
|
generate(FUNCTIONS_PATH, FUNCTIONS_BASE)
|
||||||
|
generate(BASE_PATH, BASE_BASE)
|
||||||
pyrogram_api()
|
pyrogram_api()
|
||||||
|
|
||||||
|
|
||||||
if "__main__" == __name__:
|
if "__main__" == __name__:
|
||||||
FUNCTIONS_PATH = "../../pyrogram/api/functions"
|
FUNCTIONS_PATH = "../../pyrogram/raw/functions"
|
||||||
TYPES_PATH = "../../pyrogram/api/types"
|
TYPES_PATH = "../../pyrogram/raw/types"
|
||||||
|
BASE_PATH = "../../pyrogram/raw/base"
|
||||||
HOME = "."
|
HOME = "."
|
||||||
DESTINATION = "../../docs/source/telegram"
|
DESTINATION = "../../docs/source/telegram"
|
||||||
PYROGRAM_API_DEST = "../../docs/source/api"
|
PYROGRAM_API_DEST = "../../docs/source/api"
|
||||||
|
2
compiler/docs/template/bound-methods.rst
vendored
2
compiler/docs/template/bound-methods.rst
vendored
@ -26,7 +26,7 @@ some of the required arguments.
|
|||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
.. currentmodule:: pyrogram
|
.. currentmodule:: pyrogram.types
|
||||||
|
|
||||||
Message
|
Message
|
||||||
-------
|
-------
|
||||||
|
17
compiler/docs/template/methods.rst
vendored
17
compiler/docs/template/methods.rst
vendored
@ -1,7 +1,8 @@
|
|||||||
Available Methods
|
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
|
.. code-block:: python
|
||||||
:emphasize-lines: 6
|
:emphasize-lines: 6
|
||||||
@ -34,6 +35,20 @@ Utilities
|
|||||||
|
|
||||||
{utilities}
|
{utilities}
|
||||||
|
|
||||||
|
.. currentmodule:: pyrogram
|
||||||
|
|
||||||
|
.. autosummary::
|
||||||
|
:nosignatures:
|
||||||
|
|
||||||
|
idle
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
|
||||||
|
idle
|
||||||
|
|
||||||
|
.. currentmodule:: pyrogram.Client
|
||||||
|
|
||||||
Messages
|
Messages
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
2
compiler/docs/template/types.rst
vendored
2
compiler/docs/template/types.rst
vendored
@ -19,7 +19,7 @@ This page is about Pyrogram types. All types listed here are accessible through
|
|||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
.. currentmodule:: pyrogram
|
.. currentmodule:: pyrogram.types
|
||||||
|
|
||||||
Users & Chats
|
Users & Chats
|
||||||
-------------
|
-------------
|
||||||
|
@ -28,7 +28,7 @@ PASSWORD_HASH_INVALID Two-step verification password is invalid
|
|||||||
USERNAME_NOT_OCCUPIED The username is not occupied by anyone
|
USERNAME_NOT_OCCUPIED The username is not occupied by anyone
|
||||||
USERNAME_INVALID The username is invalid
|
USERNAME_INVALID The username is invalid
|
||||||
MESSAGE_ID_INVALID The message id 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
|
ENTITY_MENTION_USER_INVALID The mentioned entity is not an user
|
||||||
MESSAGE_TOO_LONG The message text is over 4096 characters
|
MESSAGE_TOO_LONG The message text is over 4096 characters
|
||||||
ACCESS_TOKEN_EXPIRED The bot token is invalid
|
ACCESS_TOKEN_EXPIRED The bot token is invalid
|
||||||
|
|
@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||||
SPHINXOPTS =
|
SPHINXOPTS = -j $(shell nproc --all)
|
||||||
SPHINXBUILD = sphinx-build
|
SPHINXBUILD = sphinx-build
|
||||||
SPHINXPROJ = Pyrogram
|
SPHINXPROJ = Pyrogram
|
||||||
SOURCEDIR = source
|
SOURCEDIR = source
|
||||||
@ -17,4 +17,7 @@ help:
|
|||||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
%: Makefile
|
%: 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)
|
||||||
|
@ -3,4 +3,5 @@ sphinx_rtd_theme
|
|||||||
sphinx_copybutton
|
sphinx_copybutton
|
||||||
sphinx_tabs
|
sphinx_tabs
|
||||||
pypandoc
|
pypandoc
|
||||||
requests
|
requests
|
||||||
|
sphinx-autobuild
|
@ -46,7 +46,7 @@ with open("sitemap.xml", "w") as f:
|
|||||||
def search(path):
|
def search(path):
|
||||||
try:
|
try:
|
||||||
for j in os.listdir(path):
|
for j in os.listdir(path):
|
||||||
search("{}/{}".format(path, j))
|
search(f"{path}/{j}")
|
||||||
except NotADirectoryError:
|
except NotADirectoryError:
|
||||||
if not path.endswith(".rst"):
|
if not path.endswith(".rst"):
|
||||||
return
|
return
|
||||||
@ -58,7 +58,7 @@ with open("sitemap.xml", "w") as f:
|
|||||||
else:
|
else:
|
||||||
folder = path[0]
|
folder = path[0]
|
||||||
|
|
||||||
path = "{}{}".format(canonical, "/".join(path))[:-len(".rst")]
|
path = f"{canonical}{'/'.join(path)}"[:-len(".rst")]
|
||||||
|
|
||||||
if path.endswith("index"):
|
if path.endswith("index"):
|
||||||
path = path[:-len("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)
|
urls.sort(key=lambda x: x[3], reverse=True)
|
||||||
|
|
||||||
for i in urls:
|
for i in urls:
|
||||||
f.write(" <url>\n")
|
f.write(f" <url>\n")
|
||||||
f.write(" <loc>{}</loc>\n".format(i[0]))
|
f.write(f" <loc>{i[0]}</loc>\n")
|
||||||
f.write(" <lastmod>{}</lastmod>\n".format(i[1]))
|
f.write(f" <lastmod>{i[1]}</lastmod>\n")
|
||||||
f.write(" <changefreq>{}</changefreq>\n".format(i[2]))
|
f.write(f" <changefreq>{i[2]}</changefreq>\n")
|
||||||
f.write(" <priority>{}</priority>\n".format(i[3]))
|
f.write(f" <priority>{i[3]}</priority>\n")
|
||||||
f.write(" </url>\n\n")
|
f.write(f" </url>\n\n")
|
||||||
|
|
||||||
f.write("</urlset>")
|
f.write("</urlset>")
|
||||||
|
@ -26,6 +26,7 @@ functions.
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -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
|
|
7
docs/source/api/errors/bad-request.rst
Normal file
7
docs/source/api/errors/bad-request.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
400 - BadRequest
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/error/source/400_BAD_REQUEST.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
7
docs/source/api/errors/flood.rst
Normal file
7
docs/source/api/errors/flood.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
420 - Flood
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/error/source/420_FLOOD.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
7
docs/source/api/errors/forbidden.rst
Normal file
7
docs/source/api/errors/forbidden.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
403 - Forbidden
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/error/source/403_FORBIDDEN.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
37
docs/source/api/errors/index.rst
Normal file
37
docs/source/api/errors/index.rst
Normal 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
|
7
docs/source/api/errors/internal-server-error.rst
Normal file
7
docs/source/api/errors/internal-server-error.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
500 - InternalServerError
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
7
docs/source/api/errors/not-acceptable.rst
Normal file
7
docs/source/api/errors/not-acceptable.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
406 - NotAcceptable
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/error/source/406_NOT_ACCEPTABLE.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
7
docs/source/api/errors/see-other.rst
Normal file
7
docs/source/api/errors/see-other.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
303 - SeeOther
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/error/source/303_SEE_OTHER.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
7
docs/source/api/errors/unauthorized.rst
Normal file
7
docs/source/api/errors/unauthorized.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
401 - Unauthorized
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/error/source/401_UNAUTHORIZED.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
@ -1,8 +1,11 @@
|
|||||||
Update Filters
|
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
|
Details
|
||||||
-------
|
-------
|
||||||
|
|
||||||
.. autoclass:: pyrogram.Filters
|
.. automodule:: pyrogram.filters
|
||||||
:members:
|
:members:
|
||||||
|
@ -7,7 +7,8 @@ For a much more convenient way of registering callback functions have a look at
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:emphasize-lines: 1, 10
|
:emphasize-lines: 1, 10
|
||||||
|
|
||||||
from pyrogram import Client, MessageHandler
|
from pyrogram import Client
|
||||||
|
from pyrogram.handlers import MessageHandler
|
||||||
|
|
||||||
app = Client("my_account")
|
app = Client("my_account")
|
||||||
|
|
||||||
@ -22,11 +23,12 @@ For a much more convenient way of registering callback functions have a look at
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
.. currentmodule:: pyrogram
|
.. currentmodule:: pyrogram.handlers
|
||||||
|
|
||||||
Index
|
Index
|
||||||
-----
|
-----
|
||||||
|
@ -12,6 +12,7 @@ This FAQ page provides answers to common questions about Pyrogram and, to some e
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -10,6 +10,7 @@ general. Some words may as well link to dedicated articles in case the topic is
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -1,6 +1,95 @@
|
|||||||
Welcome to Pyrogram
|
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::
|
.. toctree::
|
||||||
:hidden:
|
:hidden:
|
||||||
:caption: Introduction
|
:caption: Introduction
|
||||||
@ -17,6 +106,7 @@ Welcome to Pyrogram
|
|||||||
start/invoking
|
start/invoking
|
||||||
start/updates
|
start/updates
|
||||||
start/errors
|
start/errors
|
||||||
|
start/examples/index
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:hidden:
|
:hidden:
|
||||||
@ -28,7 +118,7 @@ Welcome to Pyrogram
|
|||||||
api/bound-methods/index
|
api/bound-methods/index
|
||||||
api/handlers
|
api/handlers
|
||||||
api/decorators
|
api/decorators
|
||||||
api/errors
|
api/errors/index
|
||||||
api/filters
|
api/filters
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
@ -71,92 +161,4 @@ Welcome to Pyrogram
|
|||||||
|
|
||||||
telegram/functions/index
|
telegram/functions/index
|
||||||
telegram/types/index
|
telegram/types/index
|
||||||
|
telegram/base/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|
|
|
@ -1,18 +1,19 @@
|
|||||||
Install Guide
|
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.
|
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/.
|
- Get **pip** by following the instructions at https://pip.pypa.io/en/latest/installing/.
|
||||||
|
|
||||||
.. important::
|
.. 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
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
@ -46,43 +47,6 @@ the link):
|
|||||||
|
|
||||||
$ pip3 install -U https://github.com/pyrogram/pyrogram/archive/develop.zip
|
$ 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
|
Verifying
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
Quick Start
|
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.
|
The next few steps serve as a quick start for all new Pyrogrammers that want to see Pyrogram in action as fast as
|
||||||
Let's go!
|
possible. Let's go!
|
||||||
|
|
||||||
Get Pyrogram Real Fast
|
Get Pyrogram Real Fast
|
||||||
----------------------
|
----------------------
|
||||||
|
@ -6,6 +6,7 @@ project with the library. Let's see how it's done.
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -11,6 +11,7 @@ This is a collection of remarkable projects made with Pyrogram.
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -6,6 +6,7 @@ API calls. This section provides all the information you need in order to author
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
@ -41,7 +42,7 @@ keep the session alive, Pyrogram won't ask you again to enter your phone number.
|
|||||||
|
|
||||||
.. important::
|
.. important::
|
||||||
|
|
||||||
Your ``*.session`` files are personal and must be kept secret.
|
Your ``*.session`` file is personal and must be kept secret.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ to control the behaviour of your application. Pyrogram errors all live inside th
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
@ -17,9 +18,8 @@ to control the behaviour of your application. Pyrogram errors all live inside th
|
|||||||
RPCError
|
RPCError
|
||||||
--------
|
--------
|
||||||
|
|
||||||
The father of all errors is named ``RPCError``. This error exists in form of a Python exception which is directly
|
The father of all errors is named ``RPCError`` and is able to catch all Telegram API related errors.
|
||||||
subclass-ed from Python's main ``Exception`` and is able to catch all Telegram API related errors. This error is raised
|
This error is raised every time a method call against Telegram's API was unsuccessful.
|
||||||
every time a method call against Telegram's API was unsuccessful.
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. 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
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
from pyrogram.errors import BadRequest, Forbidden, ...
|
from pyrogram.errors import BadRequest, Forbidden, ...
|
||||||
|
|
||||||
- `303 - SeeOther <../api/errors#seeother>`_
|
- :doc:`303 - SeeOther <../api/errors/see-other>`
|
||||||
- `400 - BadRequest <../api/errors#badrequest>`_
|
- :doc:`400 - BadRequest <../api/errors/bad-request>`
|
||||||
- `401 - Unauthorized <../api/errors#unauthorized>`_
|
- :doc:`401 - Unauthorized <../api/errors/unauthorized>`
|
||||||
- `403 - Forbidden <../api/errors#forbidden>`_
|
- :doc:`403 - Forbidden <../api/errors/forbidden>`
|
||||||
- `406 - NotAcceptable <../api/errors#notacceptable>`_
|
- :doc:`406 - NotAcceptable <../api/errors/not-acceptable>`
|
||||||
- `420 - Flood <../api/errors#flood>`_
|
- :doc:`420 - Flood <../api/errors/flood>`
|
||||||
- `500 - InternalServerError <../api/errors#internalservererror>`_
|
- :doc:`500 - InternalServerError <../api/errors/internal-server-error>`
|
||||||
|
|
||||||
Single Errors
|
Single Errors
|
||||||
-------------
|
-------------
|
||||||
@ -59,7 +59,7 @@ issue. For example:
|
|||||||
from pyrogram.errors import FloodWait
|
from pyrogram.errors import FloodWait
|
||||||
|
|
||||||
These errors subclass directly from the category of errors they belong to, which in turn subclass from the father
|
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
|
- RPCError
|
||||||
- BadRequest
|
- BadRequest
|
||||||
|
61
docs/source/start/examples/bot_keyboards.rst
Normal file
61
docs/source/start/examples/bot_keyboards.rst
Normal 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"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
19
docs/source/start/examples/callback_queries.rst
Normal file
19
docs/source/start/examples/callback_queries.rst
Normal 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()
|
21
docs/source/start/examples/echobot.rst
Normal file
21
docs/source/start/examples/echobot.rst
Normal 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()
|
15
docs/source/start/examples/get_chat_members.rst
Normal file
15
docs/source/start/examples/get_chat_members.rst
Normal 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)
|
14
docs/source/start/examples/get_dialogs.rst
Normal file
14
docs/source/start/examples/get_dialogs.rst
Normal 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)
|
15
docs/source/start/examples/get_history.rst
Normal file
15
docs/source/start/examples/get_history.rst
Normal 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)
|
21
docs/source/start/examples/hello_world.rst
Normal file
21
docs/source/start/examples/hello_world.rst
Normal 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")
|
46
docs/source/start/examples/index.rst
Normal file
46
docs/source/start/examples/index.rst
Normal 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
|
61
docs/source/start/examples/inline_queries.rst
Normal file
61
docs/source/start/examples/inline_queries.rst
Normal 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()
|
18
docs/source/start/examples/raw_updates.rst
Normal file
18
docs/source/start/examples/raw_updates.rst
Normal 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()
|
18
docs/source/start/examples/use_inline_bots.rst
Normal file
18
docs/source/start/examples/use_inline_bots.rst
Normal 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)
|
33
docs/source/start/examples/welcomebot.rst
Normal file
33
docs/source/start/examples/welcomebot.rst
Normal 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()
|
@ -6,6 +6,7 @@ account; we are now aiming towards the core of the library. It's time to start p
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
@ -13,7 +14,8 @@ account; we are now aiming towards the core of the library. It's time to start p
|
|||||||
Basic Usage
|
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
|
.. 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 = Client("my_account")
|
||||||
|
|
||||||
app.start()
|
with app:
|
||||||
|
app.send_message("me", "Hi!")
|
||||||
|
|
||||||
print(app.get_me())
|
Basic step-by-step
|
||||||
app.send_message("me", "Hi, it's me!")
|
^^^^^^^^^^^^^^^^^^
|
||||||
app.send_location("me", 51.500729, -0.124583)
|
|
||||||
app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI")
|
|
||||||
|
|
||||||
app.stop()
|
#. Let's begin by importing the Client class:
|
||||||
|
|
||||||
#. Let's begin by importing the Client class from the Pyrogram package:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. 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")
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
app.start()
|
with app:
|
||||||
|
|
||||||
#. Now, you can call any method you like:
|
#. Now, you can call any method you like:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
print(app.get_me()) # Print information about yourself
|
app.send_message("me", "Hi!")
|
||||||
|
|
||||||
# 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()
|
|
||||||
|
|
||||||
Context Manager
|
Context Manager
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
You can also use Pyrogram's Client in a context manager with the ``with`` statement. The client will automatically
|
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` gracefully, even in case of unhandled exceptions in
|
:meth:`~pyrogram.Client.start` and :meth:`~pyrogram.Client.stop`, which are methods required for Pyrogram to work
|
||||||
your code. The example above can be therefore rewritten in a much nicer way:
|
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
|
.. 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")
|
app = Client("my_account")
|
||||||
|
|
||||||
with app:
|
app.start()
|
||||||
print(app.get_me())
|
app.send_message("me", "Hi!")
|
||||||
app.send_message("me", "Hi there! I'm using **Pyrogram**")
|
app.stop()
|
||||||
app.send_location("me", 51.500729, -0.124583)
|
|
||||||
app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI")
|
|
||||||
|
|
||||||
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())
|
||||||
|
@ -6,6 +6,7 @@ This page deals with updates and how to handle such events in Pyrogram. Let's ha
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
@ -24,72 +25,14 @@ function will be called back by the framework and its body executed.
|
|||||||
Registering a Handler
|
Registering a Handler
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
To explain how handlers work let's have a look at the most used one, the :class:`~pyrogram.MessageHandler`, which will
|
To explain how handlers work let's examine the one which will be in charge for handling :class:`~pyrogram.types.Message`
|
||||||
be in charge for handling :class:`~pyrogram.Message` updates coming from all around your chats. Every other handler shares
|
updates coming from all around your chats. Every other handler shares the same setup logic; you should not have
|
||||||
the same setup logic; you should not have troubles settings them up once you learn from this section.
|
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()
|
|
||||||
|
|
||||||
Using Decorators
|
Using Decorators
|
||||||
----------------
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
All of the above will become quite verbose, especially in case you have lots of handlers to register. A much nicer way
|
The most elegant way to register a message handler is by using the :meth:`~pyrogram.Client.on_message` decorator:
|
||||||
to do so is by decorating your callback function with the :meth:`~pyrogram.Client.on_message` decorator.
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@ -100,7 +43,58 @@ to do so is by decorating your callback function with the :meth:`~pyrogram.Clien
|
|||||||
|
|
||||||
@app.on_message()
|
@app.on_message()
|
||||||
def my_handler(client, message):
|
def my_handler(client, message):
|
||||||
print(message)
|
message.forward("me")
|
||||||
|
|
||||||
|
|
||||||
app.run()
|
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")
|
||||||
|
@ -10,6 +10,7 @@ Telegram API with its functions and types.
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
: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
|
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
|
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
|
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.
|
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>`
|
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
|
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.
|
correct values using named arguments.
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ Here's some examples:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from pyrogram import Client
|
from pyrogram import Client
|
||||||
from pyrogram.api import functions
|
from pyrogram.raw import functions
|
||||||
|
|
||||||
with Client("my_account") as app:
|
with Client("my_account") as app:
|
||||||
app.send(
|
app.send(
|
||||||
@ -70,7 +71,7 @@ Here's some examples:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from pyrogram import Client
|
from pyrogram import Client
|
||||||
from pyrogram.api import functions, types
|
from pyrogram.raw import functions, types
|
||||||
|
|
||||||
with Client("my_account") as app:
|
with Client("my_account") as app:
|
||||||
app.send(
|
app.send(
|
||||||
@ -85,7 +86,7 @@ Here's some examples:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from pyrogram import Client
|
from pyrogram import Client
|
||||||
from pyrogram.api import functions, types
|
from pyrogram.raw import functions, types
|
||||||
|
|
||||||
with Client("my_account") as app:
|
with Client("my_account") as app:
|
||||||
app.send(
|
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.
|
There are three different InputPeer types, one for each kind of Telegram entity.
|
||||||
Whenever an InputPeer is needed you must pass one of these:
|
Whenever an InputPeer is needed you must pass one of these:
|
||||||
|
|
||||||
- :class:`~pyrogram.api.types.InputPeerUser` - Users
|
- :class:`~pyrogram.raw.types.InputPeerUser` - Users
|
||||||
- :class:`~pyrogram.api.types.InputPeerChat` - Basic Chats
|
- :class:`~pyrogram.raw.types.InputPeerChat` - Basic Chats
|
||||||
- :class:`~pyrogram.api.types.InputPeerChannel` - Either Channels or Supergroups
|
- :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
|
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
|
:meth:`~pyrogram.Client.resolve_peer` as a convenience utility method that returns the correct InputPeer
|
||||||
|
@ -5,6 +5,7 @@ Users can interact with other bots via plain text messages as well as inline que
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -6,6 +6,7 @@ This page explains how this file is structured, how to use it and why.
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
Creating Filters
|
Creating Filters
|
||||||
================
|
================
|
||||||
|
|
||||||
Pyrogram already provides lots of built-in :class:`~pyrogram.Filters` to work with, but in case you can't find
|
Pyrogram already provides lots of built-in :class:`~pyrogram.filters` to work with, but in case you can't find a
|
||||||
a specific one for your needs or want to build a custom filter by yourself (to be used in a different kind of handler,
|
specific one for your needs or want to build a custom filter by yourself you can use :meth:`~pyrogram.filters.create`.
|
||||||
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.
|
|
||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
: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
|
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
|
:class:`~pyrogram.handlers.CallbackQueryHandler`. Note that callback queries updates are only received by bots as result
|
||||||
user pressing an inline button attached to the bot's message; create and :doc:`authorize your bot <../start/auth>`,
|
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
|
then send a message with an inline keyboard to yourself. This allows you to test your filter by pressing the inline
|
||||||
button:
|
button:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from pyrogram import InlineKeyboardMarkup, InlineKeyboardButton
|
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
app.send_message(
|
app.send_message(
|
||||||
"username", # Change this to your username or id
|
"username", # Change this to your username or id
|
||||||
@ -39,7 +35,7 @@ button:
|
|||||||
Basic Filters
|
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
|
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
|
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
|
.. 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
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import filters
|
||||||
|
|
||||||
def func(_, query):
|
def func(_, query):
|
||||||
return query.data == "pyrogram"
|
return query.data == "pyrogram"
|
||||||
|
|
||||||
static_data_filter = Filters.create(func)
|
static_data_filter = filters.create(func)
|
||||||
|
|
||||||
The filter usage remains the same:
|
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 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:
|
This is how a dynamic custom filter looks like:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import filters
|
||||||
|
|
||||||
def dynamic_data_filter(data):
|
def dynamic_data_filter(data):
|
||||||
return Filters.create(
|
return filters.create(
|
||||||
lambda flt, query: flt.data == query.data,
|
lambda flt, query: flt.data == query.data,
|
||||||
data=data # "data" kwarg is accessed with "flt.data" above
|
data=data # "data" kwarg is accessed with "flt.data" above
|
||||||
)
|
)
|
||||||
|
@ -6,6 +6,7 @@ to actually worry about -- that's normal -- and luckily for you, Pyrogram provid
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -6,6 +6,7 @@ Here we'll show some advanced usages when working with :doc:`update handlers <..
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
@ -25,21 +26,21 @@ For example, take these two handlers:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:emphasize-lines: 1, 6
|
:emphasize-lines: 1, 6
|
||||||
|
|
||||||
@app.on_message(Filters.text | Filters.sticker)
|
@app.on_message(filters.text | filters.sticker)
|
||||||
def text_or_sticker(client, message):
|
def text_or_sticker(client, message):
|
||||||
print("Text or Sticker")
|
print("Text or Sticker")
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.text)
|
@app.on_message(filters.text)
|
||||||
def just_text(client, message):
|
def just_text(client, message):
|
||||||
print("Just Text")
|
print("Just Text")
|
||||||
|
|
||||||
Here, ``just_text`` is never executed because ``text_or_sticker``, which has been registered first, already handles
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
@app.on_message(Filters.text, group=1)
|
@app.on_message(filters.text, group=1)
|
||||||
def just_text(client, message):
|
def just_text(client, message):
|
||||||
print("Just Text")
|
print("Just Text")
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ Or, if you want ``just_text`` to be executed *before* ``text_or_sticker`` (note
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@app.on_message(Filters.text, group=-1)
|
@app.on_message(filters.text, group=-1)
|
||||||
def just_text(client, message):
|
def just_text(client, message):
|
||||||
print("Just Text")
|
print("Just Text")
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ With :meth:`~pyrogram.Client.add_handler` (without decorators) the same can be a
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
app.add_handler(MessageHandler(just_text, Filters.text), -1)
|
app.add_handler(MessageHandler(just_text, filters.text), -1)
|
||||||
|
|
||||||
Update propagation
|
Update propagation
|
||||||
------------------
|
------------------
|
||||||
@ -67,17 +68,17 @@ continue to propagate the same update to the next groups until all the handlers
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@app.on_message(Filters.private)
|
@app.on_message(filters.private)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(0)
|
print(0)
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.private, group=1)
|
@app.on_message(filters.private, group=1)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
raise Exception("Unhandled exception!") # Simulate an unhandled exception
|
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):
|
def _(client, message):
|
||||||
print(2)
|
print(2)
|
||||||
|
|
||||||
@ -109,18 +110,18 @@ Example with ``stop_propagation()``:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@app.on_message(Filters.private)
|
@app.on_message(filters.private)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(0)
|
print(0)
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.private, group=1)
|
@app.on_message(filters.private, group=1)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(1)
|
print(1)
|
||||||
message.stop_propagation()
|
message.stop_propagation()
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.private, group=2)
|
@app.on_message(filters.private, group=2)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(2)
|
print(2)
|
||||||
|
|
||||||
@ -130,18 +131,18 @@ Example with ``raise StopPropagation``:
|
|||||||
|
|
||||||
from pyrogram import StopPropagation
|
from pyrogram import StopPropagation
|
||||||
|
|
||||||
@app.on_message(Filters.private)
|
@app.on_message(filters.private)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(0)
|
print(0)
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.private, group=1)
|
@app.on_message(filters.private, group=1)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(1)
|
print(1)
|
||||||
raise StopPropagation
|
raise StopPropagation
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.private, group=2)
|
@app.on_message(filters.private, group=2)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(2)
|
print(2)
|
||||||
|
|
||||||
@ -177,19 +178,19 @@ Example with ``continue_propagation()``:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@app.on_message(Filters.private)
|
@app.on_message(filters.private)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(0)
|
print(0)
|
||||||
message.continue_propagation()
|
message.continue_propagation()
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.private)
|
@app.on_message(filters.private)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(1)
|
print(1)
|
||||||
message.continue_propagation()
|
message.continue_propagation()
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.private)
|
@app.on_message(filters.private)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(2)
|
print(2)
|
||||||
|
|
||||||
@ -199,19 +200,19 @@ Example with ``raise ContinuePropagation``:
|
|||||||
|
|
||||||
from pyrogram import ContinuePropagation
|
from pyrogram import ContinuePropagation
|
||||||
|
|
||||||
@app.on_message(Filters.private)
|
@app.on_message(filters.private)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(0)
|
print(0)
|
||||||
raise ContinuePropagation
|
raise ContinuePropagation
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.private)
|
@app.on_message(filters.private)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(1)
|
print(1)
|
||||||
raise ContinuePropagation
|
raise ContinuePropagation
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.private)
|
@app.on_message(filters.private)
|
||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(2)
|
print(2)
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ actually is the MTProto and the Bot API.
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -6,6 +6,7 @@ through an intermediate SOCKS5 proxy server.
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -10,6 +10,7 @@ visit and learn from each library documentation.
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
@ -69,8 +70,7 @@ Using ``apscheduler``
|
|||||||
scheduler.start()
|
scheduler.start()
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
``apscheduler`` does also support async code, here's an example with
|
``apscheduler`` does also support async code, here's an example:
|
||||||
`Pyrogram Asyncio <https://docs.pyrogram.org/intro/install.html#asynchronous>`_:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ humans and another more compact for machines that is able to recover the origina
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -22,6 +22,7 @@ That's how a session looks like on the Android app, showing the three main piece
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -11,6 +11,7 @@ different Pyrogram applications with **minimal boilerplate code**.
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
@ -51,7 +52,8 @@ after importing your modules, like this:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. 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
|
from handlers import echo, echo_reversed
|
||||||
|
|
||||||
@ -60,19 +62,19 @@ after importing your modules, like this:
|
|||||||
app.add_handler(
|
app.add_handler(
|
||||||
MessageHandler(
|
MessageHandler(
|
||||||
echo,
|
echo,
|
||||||
Filters.text & Filters.private))
|
filters.text & filters.private))
|
||||||
|
|
||||||
app.add_handler(
|
app.add_handler(
|
||||||
MessageHandler(
|
MessageHandler(
|
||||||
echo_reversed,
|
echo_reversed,
|
||||||
Filters.text & Filters.private),
|
filters.text & filters.private),
|
||||||
group=1)
|
group=1)
|
||||||
|
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
This is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to
|
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
|
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.
|
functions. So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically.
|
||||||
|
|
||||||
Using Smart Plugins
|
Using Smart Plugins
|
||||||
@ -102,15 +104,15 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:emphasize-lines: 4, 9
|
: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):
|
def echo(client, message):
|
||||||
message.reply(message.text)
|
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):
|
def echo_reversed(client, message):
|
||||||
message.reply(message.text[::-1])
|
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
|
.. code-block:: python
|
||||||
:emphasize-lines: 5, 6
|
:emphasize-lines: 5, 6
|
||||||
|
|
||||||
@Client.on_message(Filters.text & Filters.private)
|
@Client.on_message(filters.text & filters.private)
|
||||||
def echo(client, message):
|
def echo(client, message):
|
||||||
message.reply(message.text)
|
message.reply(message.text)
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ decide to manually terminate it) and is used to authorize a client to execute AP
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -20,6 +20,7 @@ Telegram's test servers without hassle. All you need to do is start a new sessio
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -19,6 +19,7 @@ variety of decorations that can also be nested in order to combine multiple styl
|
|||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
Using Filters
|
Using Filters
|
||||||
=============
|
=============
|
||||||
|
|
||||||
So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time a specific update
|
So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time an update comes
|
||||||
comes from the server, but there's much more than that to come.
|
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.
|
updates are allowed or not to be passed in your callback functions, based on their inner details.
|
||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:backlinks: none
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
-----
|
-----
|
||||||
@ -24,10 +25,10 @@ Let's start right away with a simple example:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:emphasize-lines: 4
|
: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):
|
def my_handler(client, message):
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
@ -35,16 +36,17 @@ Let's start right away with a simple example:
|
|||||||
callback function itself:
|
callback function itself:
|
||||||
|
|
||||||
.. code-block:: python
|
.. 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):
|
def my_handler(client, message):
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
app.add_handler(MessageHandler(my_handler, Filters.audio))
|
app.add_handler(MessageHandler(my_handler, filters.audio))
|
||||||
|
|
||||||
Combining Filters
|
Combining Filters
|
||||||
-----------------
|
-----------------
|
||||||
@ -61,7 +63,7 @@ Here are some examples:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@app.on_message(Filters.text & ~Filters.edited)
|
@app.on_message(filters.text & ~filters.edited)
|
||||||
def my_handler(client, message):
|
def my_handler(client, message):
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
@ -69,21 +71,21 @@ Here are some examples:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. 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):
|
def my_handler(client, message):
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
Advanced Filters
|
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:
|
can also accept arguments:
|
||||||
|
|
||||||
- Message is either a */start* or */help* **command**.
|
- Message is either a */start* or */help* **command**.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@app.on_message(Filters.command(["start", "help"]))
|
@app.on_message(filters.command(["start", "help"]))
|
||||||
def my_handler(client, message):
|
def my_handler(client, message):
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
@ -91,7 +93,7 @@ can also accept arguments:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@app.on_message(Filters.regex("pyrogram"))
|
@app.on_message(filters.regex("pyrogram"))
|
||||||
def my_handler(client, message):
|
def my_handler(client, message):
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
@ -99,16 +101,16 @@ More handlers using different filters can also live together.
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@app.on_message(Filters.command("start"))
|
@app.on_message(filters.command("start"))
|
||||||
def start_command(client, message):
|
def start_command(client, message):
|
||||||
print("This is the /start command")
|
print("This is the /start command")
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.command("help"))
|
@app.on_message(filters.command("help"))
|
||||||
def help_command(client, message):
|
def help_command(client, message):
|
||||||
print("This is the /help command")
|
print("This is the /help command")
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.chat("PyrogramChat"))
|
@app.on_message(filters.chat("PyrogramChat"))
|
||||||
def from_pyrogramchat(client, message):
|
def from_pyrogramchat(client, message):
|
||||||
print("New message in @PyrogramChat")
|
print("New message in @PyrogramChat")
|
||||||
|
121
examples/LICENSE
121
examples/LICENSE
@ -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.
|
|
@ -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)
|
|
@ -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"
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
@ -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()
|
|
@ -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()
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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")
|
|
@ -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()
|
|
@ -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()
|
|
@ -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)
|
|
@ -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()
|
|
@ -16,10 +16,23 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# 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+)"
|
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
|
||||||
__copyright__ = "Copyright (C) 2017-2020 Dan <https://github.com/delivrance>"
|
__copyright__ = "Copyright (C) 2017-2020 Dan <https://github.com/delivrance>"
|
||||||
|
|
||||||
from .client import *
|
|
||||||
from .client.handlers import *
|
class StopTransmission(StopAsyncIteration):
|
||||||
from .client.types import *
|
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
1069
pyrogram/client.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
@ -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
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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")
|
|
@ -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)
|
|
@ -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
|
|
@ -68,7 +68,7 @@ class TCP:
|
|||||||
password=proxy.get("password", None)
|
password=proxy.get("password", None)
|
||||||
)
|
)
|
||||||
|
|
||||||
log.info("Using proxy {}:{}".format(hostname, port))
|
log.info(f"Using proxy {hostname}:{port}")
|
||||||
else:
|
else:
|
||||||
self.socket = socks.socksocket(
|
self.socket = socks.socksocket(
|
||||||
socket.AF_INET6 if ipv6
|
socket.AF_INET6 if ipv6
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from pyrogram.crypto import aes
|
||||||
from .tcp import TCP
|
from .tcp import TCP
|
||||||
from ....crypto.aes import AES
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ class TCPAbridgedO(TCP):
|
|||||||
self.encrypt = (nonce[8:40], nonce[40:56], bytearray(1))
|
self.encrypt = (nonce[8:40], nonce[40:56], bytearray(1))
|
||||||
self.decrypt = (temp[0:32], temp[32:48], 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)
|
await super().send(nonce)
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ class TCPAbridgedO(TCP):
|
|||||||
length = len(data) // 4
|
length = len(data) // 4
|
||||||
|
|
||||||
await super().send(
|
await super().send(
|
||||||
AES.ctr256_encrypt(
|
aes.ctr256_encrypt(
|
||||||
(bytes([length])
|
(bytes([length])
|
||||||
if length <= 126
|
if length <= 126
|
||||||
else b"\x7f" + length.to_bytes(3, "little"))
|
else b"\x7f" + length.to_bytes(3, "little"))
|
||||||
@ -72,7 +72,7 @@ class TCPAbridgedO(TCP):
|
|||||||
if length is None:
|
if length is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
length = AES.ctr256_decrypt(length, *self.decrypt)
|
length = aes.ctr256_decrypt(length, *self.decrypt)
|
||||||
|
|
||||||
if length == b"\x7f":
|
if length == b"\x7f":
|
||||||
length = await super().recv(3)
|
length = await super().recv(3)
|
||||||
@ -80,11 +80,11 @@ class TCPAbridgedO(TCP):
|
|||||||
if length is None:
|
if length is None:
|
||||||
return 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)
|
data = await super().recv(int.from_bytes(length, "little") * 4)
|
||||||
|
|
||||||
if data is None:
|
if data is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return AES.ctr256_decrypt(data, *self.decrypt)
|
return aes.ctr256_decrypt(data, *self.decrypt)
|
||||||
|
@ -20,8 +20,8 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from struct import pack, unpack
|
from struct import pack, unpack
|
||||||
|
|
||||||
|
from pyrogram.crypto import aes
|
||||||
from .tcp import TCP
|
from .tcp import TCP
|
||||||
from ....crypto.aes import AES
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -50,13 +50,13 @@ class TCPIntermediateO(TCP):
|
|||||||
self.encrypt = (nonce[8:40], nonce[40:56], bytearray(1))
|
self.encrypt = (nonce[8:40], nonce[40:56], bytearray(1))
|
||||||
self.decrypt = (temp[0:32], temp[32:48], 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)
|
await super().send(nonce)
|
||||||
|
|
||||||
async def send(self, data: bytes, *args):
|
async def send(self, data: bytes, *args):
|
||||||
await super().send(
|
await super().send(
|
||||||
AES.ctr256_encrypt(
|
aes.ctr256_encrypt(
|
||||||
pack("<i", len(data)) + data,
|
pack("<i", len(data)) + data,
|
||||||
*self.encrypt
|
*self.encrypt
|
||||||
)
|
)
|
||||||
@ -68,11 +68,11 @@ class TCPIntermediateO(TCP):
|
|||||||
if length is None:
|
if length is None:
|
||||||
return 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])
|
data = await super().recv(unpack("<i", length)[0])
|
||||||
|
|
||||||
if data is None:
|
if data is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return AES.ctr256_decrypt(data, *self.decrypt)
|
return aes.ctr256_decrypt(data, *self.decrypt)
|
||||||
|
@ -15,9 +15,3 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# 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
|
|
||||||
|
@ -26,30 +26,28 @@ try:
|
|||||||
log.info("Using TgCrypto")
|
log.info("Using TgCrypto")
|
||||||
|
|
||||||
|
|
||||||
class AES:
|
def ige256_encrypt(data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||||
@classmethod
|
return tgcrypto.ige256_encrypt(data, key, iv)
|
||||||
def ige256_encrypt(cls, 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 ige256_decrypt(data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||||
def ctr256_encrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
|
return tgcrypto.ige256_decrypt(data, key, iv)
|
||||||
return tgcrypto.ctr256_encrypt(data, key, iv, state or bytearray(1))
|
|
||||||
|
|
||||||
@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 ctr256_encrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
|
||||||
def xor(a: bytes, b: bytes) -> bytes:
|
return tgcrypto.ctr256_encrypt(data, key, iv, state or bytearray(1))
|
||||||
return int.to_bytes(
|
|
||||||
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
|
|
||||||
len(a),
|
def ctr256_decrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
|
||||||
"big",
|
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:
|
except ImportError:
|
||||||
import pyaes
|
import pyaes
|
||||||
|
|
||||||
@ -60,75 +58,73 @@ except ImportError:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AES:
|
def ige256_encrypt(data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||||
@classmethod
|
return ige(data, key, iv, True)
|
||||||
def ige256_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
|
||||||
return cls.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 ige256_decrypt(data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||||
def ctr256_encrypt(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
|
return ige(data, key, iv, False)
|
||||||
return cls.ctr(data, key, iv, state or bytearray(1))
|
|
||||||
|
|
||||||
@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 ctr256_encrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
|
||||||
def xor(a: bytes, b: bytes) -> bytes:
|
return ctr(data, key, iv, state or bytearray(1))
|
||||||
return int.to_bytes(
|
|
||||||
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
|
|
||||||
len(a),
|
|
||||||
"big",
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
|
|
||||||
cipher = pyaes.AES(key)
|
|
||||||
|
|
||||||
iv_1 = iv[:16]
|
def ctr256_decrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
|
||||||
iv_2 = iv[16:]
|
return ctr(data, key, iv, state or bytearray(1))
|
||||||
|
|
||||||
data = [data[i: i + 16] for i in range(0, len(data), 16)]
|
|
||||||
|
|
||||||
if encrypt:
|
def xor(a: bytes, b: bytes) -> bytes:
|
||||||
for i, chunk in enumerate(data):
|
return int.to_bytes(
|
||||||
iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2)
|
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
|
||||||
iv_2 = chunk
|
len(a),
|
||||||
else:
|
"big",
|
||||||
for i, chunk in enumerate(data):
|
)
|
||||||
iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1)
|
|
||||||
iv_1 = chunk
|
|
||||||
|
|
||||||
return b"".join(data)
|
|
||||||
|
|
||||||
@classmethod
|
def ige(data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
|
||||||
def ctr(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray) -> bytes:
|
cipher = pyaes.AES(key)
|
||||||
cipher = pyaes.AES(key)
|
|
||||||
|
|
||||||
out = bytearray(data)
|
iv_1 = iv[:16]
|
||||||
chunk = cipher.encrypt(iv)
|
iv_2 = iv[16:]
|
||||||
|
|
||||||
for i in range(0, len(data), 16):
|
data = [data[i: i + 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]]
|
|
||||||
|
|
||||||
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:
|
return b"".join(data)
|
||||||
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)
|
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
|
||||||
|
@ -20,43 +20,54 @@ from hashlib import sha256
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from os import urandom
|
from os import urandom
|
||||||
|
|
||||||
from pyrogram.api.core import Message, Long
|
from pyrogram.raw.core import Message, Long
|
||||||
from . import AES, KDF
|
from . import aes
|
||||||
|
|
||||||
|
|
||||||
class MTProto:
|
def kdf(auth_key: bytes, msg_key: bytes, outgoing: bool) -> tuple:
|
||||||
@staticmethod
|
# https://core.telegram.org/mtproto/description#defining-aes-key-and-initialization-vector
|
||||||
def pack(message: Message, salt: int, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> bytes:
|
x = 0 if outgoing else 8
|
||||||
data = Long(salt) + session_id + message.write()
|
|
||||||
padding = urandom(-(len(data) + 12) % 16 + 12)
|
|
||||||
|
|
||||||
# 88 = 88 + 0 (outgoing message)
|
sha256_a = sha256(msg_key + auth_key[x: x + 36]).digest()
|
||||||
msg_key_large = sha256(auth_key[88: 88 + 32] + data + padding).digest()
|
sha256_b = sha256(auth_key[x + 40:x + 76] + msg_key).digest() # 76 = 40 + 36
|
||||||
msg_key = msg_key_large[8:24]
|
|
||||||
aes_key, aes_iv = KDF(auth_key, msg_key, True)
|
|
||||||
|
|
||||||
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
|
return aes_key, aes_iv
|
||||||
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
|
def pack(message: Message, salt: int, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> bytes:
|
||||||
assert data.read(8) == session_id
|
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
|
return auth_key_id + msg_key + aes.ige256_encrypt(data + padding, aes_key, aes_iv)
|
||||||
# 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
|
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
Loading…
Reference in New Issue
Block a user