2
0
mirror of https://github.com/pyrogram/pyrogram synced 2025-09-01 06:45:39 +00:00

Docs revamp. Part 2

This commit is contained in:
Dan
2019-05-10 16:14:10 +02:00
parent 559eaa2d03
commit e4b0a78f1a
33 changed files with 447 additions and 315 deletions

View File

@@ -0,0 +1,136 @@
Advanced Usage
==============
Pyrogram's API, which consists of well documented convenience methods_ and facade types_, exists to provide a much
easier interface to the undocumented and often confusing Telegram API.
In this section, you'll be shown the alternative way of communicating with Telegram using Pyrogram: the main "raw"
Telegram API with its functions and types.
Telegram Raw API
----------------
If you can't find a high-level method for your needs or if you want complete, low-level access to the whole
Telegram API, you have to use the raw :mod:`functions <pyrogram.api.functions>` and :mod:`types <pyrogram.api.types>`.
As already hinted, raw functions and types can be really confusing, mainly because people don't realize soon enough they
accept *only* the right types and that all required parameters must be filled in. This section will therefore explain
some pitfalls to take into consideration when working with the raw API.
.. hint::
Every available high-level methods in Pyrogram is built on top of these raw functions.
Nothing stops you from using the raw functions only, but they are rather complex and `plenty of them`_ are already
re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API (yet much more
powerful).
If you think a raw function should be wrapped and added as a high-level method, feel free to ask in our Community_!
Invoking Functions
^^^^^^^^^^^^^^^^^^
Unlike the methods_ found in Pyrogram's API, which can be called in the usual simple way, functions to be invoked from
the raw Telegram API have a different way of usage and are more complex.
First of all, both `raw functions`_ and `raw types`_ live in their respective packages (and sub-packages):
``pyrogram.api.functions``, ``pyrogram.api.types``. They all exist as Python classes, meaning you need to create an
instance of each every time you need them and fill them in with the correct values using named arguments.
Next, to actually invoke the raw function you have to use the :meth:`send() <pyrogram.Client.send>` method provided by
the Client class and pass the function object you created.
Here's some examples:
- Update first name, last name and bio:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions
with Client("my_account") as app:
app.send(
functions.account.UpdateProfile(
first_name="Dan", last_name="Tès",
about="Bio written from Pyrogram"
)
)
- Disable links to your account when someone forwards your messages:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions, types
with Client("my_account") as app:
app.send(
functions.account.SetPrivacy(
key=types.PrivacyKeyForwards(),
rules=[types.InputPrivacyValueDisallowAll()]
)
)
- Invite users to your channel/supergroup:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions, types
with Client("my_account") as app:
app.send(
functions.channels.InviteToChannel(
channel=app.resolve_peer(123456789), # ID or Username
users=[ # The users you want to invite
app.resolve_peer(23456789), # By ID
app.resolve_peer("username"), # By username
app.resolve_peer("+393281234567"), # By phone number
]
)
)
Chat IDs
^^^^^^^^
The way Telegram works makes it impossible to directly send a message to a user or a chat by using their IDs only.
Instead, a pair of ``id`` and ``access_hash`` wrapped in a so called ``InputPeer`` is always needed. Pyrogram allows
sending messages with IDs only thanks to cached access hashes.
There are three different InputPeer types, one for each kind of Telegram entity.
Whenever an InputPeer is needed you must pass one of these:
- `InputPeerUser <https://docs.pyrogram.ml/types/InputPeerUser>`_ - Users
- `InputPeerChat <https://docs.pyrogram.ml/types/InputPeerChat>`_ - Basic Chats
- `InputPeerChannel <https://docs.pyrogram.ml/types/InputPeerChannel>`_ - Either Channels or Supergroups
But you don't necessarily have to manually instantiate each object because, luckily for you, Pyrogram already provides
:meth:`resolve_peer() <pyrogram.Client.resolve_peer>` as a convenience utility method that returns the correct InputPeer
by accepting a peer ID only.
Another thing to take into consideration about chat IDs is the way they are represented: they are all integers and
all positive within their respective raw types.
Things are different when working with Pyrogram's API because having them in the same space can theoretically lead to
collisions, and that's why Pyrogram (as well as the official Bot API) uses a slightly different representation for each
kind of ID.
For example, given the ID *123456789*, here's how Pyrogram can tell entities apart:
- ``+ID`` User: *123456789*
- ``-ID`` Chat: *-123456789*
- ``-100ID`` Channel (and Supergroup): *-100123456789*
So, every time you take a raw ID, make sure to translate it into the correct ID when you want to use it with an
high-level method.
.. _methods: ../pyrogram/Client.html#messages
.. _types: ../pyrogram/Types.html
.. _plenty of them: ../pyrogram/Client.html#messages
.. _raw functions: ../pyrogram/functions
.. _raw types: ../pyrogram/types
.. _Community: https://t.me/PyrogramChat

View File

@@ -0,0 +1,68 @@
Auto Authorization
==================
Manually writing phone number, phone code and password on the terminal every time you want to login can be tedious.
Pyrogram is able to automate both **Log In** and **Sign Up** processes, all you need to do is pass the relevant
parameters when creating a new :class:`Client <pyrogram.Client>`.
.. note:: If you omit any of the optional parameter required for the authorization, Pyrogram will ask you to
manually write it. For instance, if you don't want to set a ``last_name`` when creating a new account you
have to explicitly pass an empty string ""; the default value (None) will trigger the input() call.
Log In
-------
To automate the **Log In** process, pass your ``phone_number`` and ``password`` (if you have one) in the Client parameters.
If you want to retrieve the phone code programmatically, pass a callback function in the ``phone_code`` field — this
function accepts a single positional argument (phone_number) and must return the correct phone code (e.g., "12345")
— otherwise, ignore this parameter, Pyrogram will ask you to input the phone code manually.
Example:
.. code-block:: python
from pyrogram import Client
def phone_code_callback(phone_number):
code = ... # Get your code programmatically
return code # e.g., "12345"
app = Client(
session_name="example",
phone_number="39**********",
phone_code=phone_code_callback, # Note the missing parentheses
password="password" # (if you have one)
)
with app:
print(app.get_me())
Sign Up
-------
To automate the **Sign Up** process (i.e., automatically create a new Telegram account), simply fill **both**
``first_name`` and ``last_name`` fields alongside the other parameters; they will be used to automatically create a new
Telegram account in case the phone number you passed is not registered yet.
Example:
.. code-block:: python
from pyrogram import Client
def phone_code_callback(phone_number):
code = ... # Get your code programmatically
return code # e.g., "12345"
app = Client(
session_name="example",
phone_number="39**********",
phone_code=phone_code_callback, # Note the missing parentheses
first_name="Pyrogram",
last_name="" # Can be an empty string
)
with app:
print(app.get_me())

View File

@@ -0,0 +1,44 @@
Bots Interaction
================
Users can interact with other bots via plain text messages as well as inline queries.
Inline Bots
-----------
- If a bot accepts inline queries, you can call it by using
:meth:`get_inline_bot_results() <pyrogram.Client.get_inline_bot_results>` to get the list of its inline results
for a query:
.. code-block:: python
# Get bot results for "Fuzz Universe" from the inline bot @vid
bot_results = app.get_inline_bot_results("vid", "Fuzz Universe")
.. figure:: https://i.imgur.com/IAqLs54.png
:width: 90%
:align: center
:figwidth: 60%
``get_inline_bot_results()`` is the equivalent action of writing ``@vid Fuzz Universe`` and getting the
results list.
- After you retrieved the bot results, you can use
:meth:`send_inline_bot_result() <pyrogram.Client.send_inline_bot_result>` to send a chosen result to any chat:
.. code-block:: python
# Send the first result to your own chat
app.send_inline_bot_result(
"me",
bot_results.query_id,
bot_results.results[0].id
)
.. figure:: https://i.imgur.com/wwxr7B7.png
:width: 90%
:align: center
:figwidth: 60%
``send_inline_bot_result()`` is the equivalent action of choosing a result from the list and sending it
to a chat.

View File

@@ -0,0 +1,11 @@
Changelog
=========
Currently, all Pyrogram release notes live inside the GitHub repository web page:
https://github.com/pyrogram/pyrogram/releases
(You will be automatically redirected in 10 seconds.)
.. raw:: html
<meta http-equiv="refresh" content="10; URL=https://github.com/pyrogram/pyrogram/releases"/>

View File

@@ -0,0 +1,90 @@
Configuration File
==================
As already mentioned in previous sections, Pyrogram can be configured by the use of an INI file.
This page explains how this file is structured in Pyrogram, how to use it and why.
Introduction
------------
The idea behind using a configuration file is to help keeping your code free of private settings information such as
the API Key and Proxy without having you to even deal with how to load such settings. The configuration file, usually
referred as ``config.ini`` file, is automatically loaded from the root of your working directory; all you need to do is
fill in the necessary parts.
.. note::
The configuration file is optional, but recommended. If, for any reason, you prefer not to use it, there's always an
alternative way to configure Pyrogram via Client's parameters. Doing so, you can have full control on how to store
and load your settings (e.g.: from environment variables).
Settings specified via Client's parameter have higher priority and will override any setting stored in the
configuration file.
The config.ini File
-------------------
By default, Pyrogram will look for a file named ``config.ini`` placed at the root of your working directory, that is,
the same folder of your running script. You can change the name or location of your configuration file by specifying it
in your Client's parameter *config_file*.
- Replace the default *config.ini* file with *my_configuration.ini*:
.. code-block:: python
from pyrogram import Client
app = Client("my_account", config_file="my_configuration.ini")
Configuration Sections
----------------------
These are all the sections Pyrogram uses in its configuration file:
Pyrogram
^^^^^^^^
The ``[pyrogram]`` section contains your Telegram API credentials: *api_id* and *api_hash*.
.. code-block:: ini
[pyrogram]
api_id = 12345
api_hash = 0123456789abcdef0123456789abcdef
`More info about API Key. <../start/Setup.html#configuration>`_
Proxy
^^^^^
The ``[proxy]`` section contains settings about your SOCKS5 proxy.
.. code-block:: ini
[proxy]
enabled = True
hostname = 11.22.33.44
port = 1080
username = <your_username>
password = <your_password>
`More info about SOCKS5 Proxy. <SOCKS5Proxy.html>`_
Plugins
^^^^^^^
The ``[plugins]`` section contains settings about Smart Plugins.
.. code-block:: ini
[plugins]
root = plugins
include =
module
folder.module
exclude =
module fn2
`More info about Smart Plugins. <SmartPlugins.html>`_

View File

@@ -0,0 +1,66 @@
Customize Sessions
==================
As you may probably know, Telegram allows users (and bots) having more than one session (authorizations) registered
in the system at the same time.
Briefly explaining, sessions are simply new logins in your account. They can be reviewed in the settings of an official
app (or by invoking `GetAuthorizations <../functions/account/GetAuthorizations.html>`_ with Pyrogram). They store some
useful information such as the client who's using them and from which country and IP address.
.. figure:: https://i.imgur.com/lzGPCdZ.png
:width: 70%
:align: center
**A Pyrogram session running on Linux, Python 3.6.**
That's how a session looks like on the Android app, showing the three main pieces of information.
- ``app_version``: **Pyrogram 🔥 0.7.5**
- ``device_model``: **CPython 3.6.5**
- ``system_version``: **Linux 4.15.0-23-generic**
Set Custom Values
-----------------
To set custom values, you can either make use of the ``config.ini`` file, this way:
.. code-block:: ini
[pyrogram]
app_version = 1.2.3
device_model = PC
system_version = Linux
Or, pass the arguments directly in the Client's constructor.
.. code-block:: python
app = Client(
"my_account",
app_version="1.2.3",
device_model="PC",
system_version="Linux"
)
Set Custom Languages
--------------------
To tell Telegram in which language should speak to you (terms of service, bots, service messages, ...) you can
set ``lang_code`` in `ISO 639-1 <https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes>`_ standard (defaults to "en",
English).
With the following code we make Telegram know we want it to speak in Italian (it):
.. code-block:: ini
[pyrogram]
lang_code = it
.. code-block:: python
app = Client(
"my_account",
lang_code="it",
)

View File

@@ -0,0 +1,59 @@
Error Handling
==============
Errors are inevitable when working with the API, and they must be correctly handled with ``try..except`` blocks.
There are many errors that Telegram could return, but they all fall in one of these categories
(which are in turn children of the :obj:`RPCError <pyrogram.RPCError>` superclass):
- :obj:`303 - See Other <pyrogram.errors.SeeOther>`
- :obj:`400 - Bad Request <pyrogram.errors.BadRequest>`
- :obj:`401 - Unauthorized <pyrogram.errors.Unauthorized>`
- :obj:`403 - Forbidden <pyrogram.errors.Forbidden>`
- :obj:`406 - Not Acceptable <pyrogram.errors.NotAcceptable>`
- :obj:`420 - Flood <pyrogram.errors.Flood>`
- :obj:`500 - Internal Server Error <pyrogram.errors.InternalServerError>`
As stated above, there are really many (too many) errors, and in case Pyrogram does not know anything yet about a
specific one, it raises a special :obj:`520 Unknown Error <pyrogram.errors.UnknownError>` exception and logs it
in the ``unknown_errors.txt`` file. Users are invited to report these unknown errors; in later versions of Pyrogram
some kind of automatic error reporting module might be implemented.
Examples
--------
.. code-block:: python
from pyrogram.errors import (
BadRequest, Flood, InternalServerError,
SeeOther, Unauthorized, UnknownError
)
try:
...
except BadRequest:
pass
except Flood:
pass
except InternalServerError:
pass
except SeeOther:
pass
except Unauthorized:
pass
except UnknownError:
pass
Exception objects may also contain some informative values.
E.g.: :obj:`FloodWait <pyrogram.errors.exceptions.flood_420.FloodWait>` holds the amount of seconds you have to wait
before you can try again. The value is always stored in the ``x`` field of the returned exception object:
.. code-block:: python
import time
from pyrogram.errors import FloodWait
try:
...
except FloodWait as e:
time.sleep(e.x)

View File

@@ -0,0 +1,222 @@
More on Updates
===============
Here we'll show some advanced usages when working with `update handlers`_ and `filters`_.
Handler Groups
--------------
If you register handlers with overlapping (conflicting) filters, only the first one is executed and any other handler
will be ignored. This is intended by design.
In order to handle the very same update more than once, you have to register your handler in a different dispatching
group. Dispatching groups hold one or more handlers and are processed sequentially, they are identified by a number
(number 0 being the default) and sorted, that is, a lower group number has a higher priority:
For example, take these two handlers:
.. code-block:: python
:emphasize-lines: 1, 6
@app.on_message(Filters.text | Filters.sticker)
def text_or_sticker(client, message):
print("Text or Sticker")
@app.on_message(Filters.text)
def just_text(client, message):
print("Just Text")
Here, ``just_text`` is never executed because ``text_or_sticker``, which has been registered first, already handles
texts (``Filters.text`` is shared and conflicting). To enable it, register the function using a different group:
.. code-block:: python
@app.on_message(Filters.text, group=1)
def just_text(client, message):
print("Just Text")
Or, if you want ``just_text`` to be fired *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``):
.. code-block:: python
@app.on_message(Filters.text, group=-1)
def just_text(client, message):
print("Just Text")
With :meth:`add_handler() <pyrogram.Client.add_handler>` (without decorators) the same can be achieved with:
.. code-block:: python
app.add_handler(MessageHandler(just_text, Filters.text), -1)
Update propagation
------------------
Registering multiple handlers, each in a different group, becomes useful when you want to handle the same update more
than once. Any incoming update will be sequentially processed by all of your registered functions by respecting the
groups priority policy described above. Even in case any handler raises an unhandled exception, Pyrogram will still
continue to propagate the same update to the next groups until all the handlers are done. Example:
.. code-block:: python
@app.on_message(Filters.private)
def _(client, message):
print(0)
@app.on_message(Filters.private, group=1)
def _(client, message):
raise Exception("Unhandled exception!") # Simulate an unhandled exception
@app.on_message(Filters.private, group=2)
def _(client, message):
print(2)
All these handlers will handle the same kind of messages, that are, messages sent or received in private chats.
The output for each incoming update will therefore be:
.. code-block:: text
0
Exception: Unhandled exception!
2
Stop Propagation
^^^^^^^^^^^^^^^^
In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following:
- Call the update's bound-method ``.stop_propagation()`` (preferred way).
- Manually ``raise StopPropagation`` exception (more suitable for raw updates only).
.. note::
Internally, the propagation is stopped by handling a custom exception. ``.stop_propagation()`` is just an elegant
and intuitive way to ``raise StopPropagation``; this also means that any code coming *after* calling the method
won't be executed as your function just raised an exception to signal the dispatcher not to propagate the
update anymore.
Example with ``stop_propagation()``:
.. code-block:: python
@app.on_message(Filters.private)
def _(client, message):
print(0)
@app.on_message(Filters.private, group=1)
def _(client, message):
print(1)
message.stop_propagation()
@app.on_message(Filters.private, group=2)
def _(client, message):
print(2)
Example with ``raise StopPropagation``:
.. code-block:: python
from pyrogram import StopPropagation
@app.on_message(Filters.private)
def _(client, message):
print(0)
@app.on_message(Filters.private, group=1)
def _(client, message):
print(1)
raise StopPropagation
@app.on_message(Filters.private, group=2)
def _(client, message):
print(2)
Each handler is registered in a different group, but the handler in group number 2 will never be executed because the
propagation was stopped earlier. The output of both (equivalent) examples will be:
.. code-block:: text
0
1
Continue Propagation
^^^^^^^^^^^^^^^^^^^^
As opposed to `stopping the update propagation <#stop-propagation>`_ and also as an alternative to the
`handler groups <#handler-groups>`_, you can signal the internal dispatcher to continue the update propagation within
**the same group** despite having conflicting filters in the next registered handler. This allows you to register
multiple handlers with overlapping filters in the same group; to let the dispatcher process the next handler you can do
*one* of the following in each handler you want to grant permission to continue:
- Call the update's bound-method ``.continue_propagation()`` (preferred way).
- Manually ``raise ContinuePropagation`` exception (more suitable for raw updates only).
.. note::
Internally, the propagation is continued by handling a custom exception. ``.continue_propagation()`` is just an
elegant and intuitive way to ``raise ContinuePropagation``; this also means that any code coming *after* calling the
method won't be executed as your function just raised an exception to signal the dispatcher to continue with the
next available handler.
Example with ``continue_propagation()``:
.. code-block:: python
@app.on_message(Filters.private)
def _(client, message):
print(0)
message.continue_propagation()
@app.on_message(Filters.private)
def _(client, message):
print(1)
message.continue_propagation()
@app.on_message(Filters.private)
def _(client, message):
print(2)
Example with ``raise ContinuePropagation``:
.. code-block:: python
from pyrogram import ContinuePropagation
@app.on_message(Filters.private)
def _(client, message):
print(0)
raise ContinuePropagation
@app.on_message(Filters.private)
def _(client, message):
print(1)
raise ContinuePropagation
@app.on_message(Filters.private)
def _(client, message):
print(2)
Three handlers are registered in the same group, and all of them will be executed because the propagation was continued
in each handler (except in the last one, where is useless to do so since there is no more handlers after).
The output of both (equivalent) examples will be:
.. code-block:: text
0
1
2
.. _`update handlers`: UpdateHandling.html
.. _`filters`: UsingFilters.html

View File

@@ -0,0 +1,356 @@
Smart Plugins
=============
Pyrogram embeds a **smart**, lightweight yet powerful plugin system that is meant to further simplify the organization
of large projects and to provide a way for creating pluggable (modular) components that can be **easily shared** across
different Pyrogram applications with **minimal boilerplate code**.
.. tip::
Smart Plugins are completely optional and disabled by default.
Introduction
------------
Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize
your applications, you had to put your function definitions in separate files and register them inside your main script
after importing your modules, like this:
.. note::
This is an example application that replies in private chats with two messages: one containing the same
text message you sent and the other containing the reversed text message.
Example: *"Pyrogram"* replies with *"Pyrogram"* and *"margoryP"*
.. code-block:: text
myproject/
config.ini
handlers.py
main.py
- ``handlers.py``
.. code-block:: python
def echo(client, message):
message.reply(message.text)
def echo_reversed(client, message):
message.reply(message.text[::-1])
- ``main.py``
.. code-block:: python
from pyrogram import Client, MessageHandler, Filters
from handlers import echo, echo_reversed
app = Client("my_account")
app.add_handler(
MessageHandler(
echo,
Filters.text & Filters.private))
app.add_handler(
MessageHandler(
echo_reversed,
Filters.text & Filters.private),
group=1)
app.run()
This is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to
manually ``import``, manually :meth:`add_handler <pyrogram.Client.add_handler>` and manually instantiate each
:obj:`MessageHandler <pyrogram.MessageHandler>` object because **you can't use those cool decorators** for your
functions. So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically.
Using Smart Plugins
-------------------
Setting up your Pyrogram project to accommodate Smart Plugins is pretty straightforward:
#. Create a new folder to store all the plugins (e.g.: "plugins", "handlers", ...).
#. Put your python files full of plugins inside. Organize them as you wish.
#. Enable plugins in your Client or via the *config.ini* file.
.. note::
This is the same example application `as shown above <#introduction>`_, written using the Smart Plugin system.
.. code-block:: text
:emphasize-lines: 2, 3
myproject/
plugins/
handlers.py
config.ini
main.py
- ``plugins/handlers.py``
.. code-block:: python
:emphasize-lines: 4, 9
from pyrogram import Client, Filters
@Client.on_message(Filters.text & Filters.private)
def echo(client, message):
message.reply(message.text)
@Client.on_message(Filters.text & Filters.private, group=1)
def echo_reversed(client, message):
message.reply(message.text[::-1])
- ``config.ini``
.. code-block:: ini
[plugins]
root = plugins
- ``main.py``
.. code-block:: python
from pyrogram import Client
Client("my_account").run()
Alternatively, without using the *config.ini* file:
.. code-block:: python
from pyrogram import Client
plugins = dict(root="plugins")
Client("my_account", plugins=plugins).run()
The first important thing to note is the new ``plugins`` folder. You can put *any python file* in *any subfolder* and
each file can contain *any decorated function* (handlers) with one limitation: within a single module (file) you must
use different names for each decorated function.
The second thing is telling Pyrogram where to look for your plugins: you can either use the *config.ini* file or
the Client parameter "plugins"; the *root* value must match the name of your plugins root folder. Your Pyrogram Client
instance will **automatically** scan the folder upon starting to search for valid handlers and register them for you.
Then you'll notice you can now use decorators. That's right, you can apply the usual decorators to your callback
functions in a static way, i.e. **without having the Client instance around**: simply use ``@Client`` (Client class)
instead of the usual ``@app`` (Client instance) and things will work just the same.
Specifying the Plugins to include
---------------------------------
By default, if you don't explicitly supply a list of plugins, every valid one found inside your plugins root folder will
be included by following the alphabetical order of the directory structure (files and subfolders); the single handlers
found inside each module will be, instead, loaded in the order they are defined, from top to bottom.
.. note::
Remember: there can be at most one handler, within a group, dealing with a specific update. Plugins with overlapping
filters included a second time will not work. Learn more at `More on Updates <MoreOnUpdates.html>`_.
This default loading behaviour is usually enough, but sometimes you want to have more control on what to include (or
exclude) and in which exact order to load plugins. The way to do this is to make use of ``include`` and ``exclude``
directives, either in the *config.ini* file or in the dictionary passed as Client argument. Here's how they work:
- If both ``include`` and ``exclude`` are omitted, all plugins are loaded as described above.
- If ``include`` is given, only the specified plugins will be loaded, in the order they are passed.
- If ``exclude`` is given, the plugins specified here will be unloaded.
The ``include`` and ``exclude`` value is a **list of strings**. Each string containing the path of the module relative
to the plugins root folder, in Python notation (dots instead of slashes).
E.g.: ``subfolder.module`` refers to ``plugins/subfolder/module.py``, with ``root="plugins"``.
You can also choose the order in which the single handlers inside a module are loaded, thus overriding the default
top-to-bottom loading policy. You can do this by appending the name of the functions to the module path, each one
separated by a blank space.
E.g.: ``subfolder.module fn2 fn1 fn3`` will load *fn2*, *fn1* and *fn3* from *subfolder.module*, in this order.
Examples
^^^^^^^^
Given this plugins folder structure with three modules, each containing their own handlers (fn1, fn2, etc...), which are
also organized in subfolders:
.. code-block:: text
myproject/
plugins/
subfolder1/
plugins1.py
- fn1
- fn2
- fn3
subfolder2/
plugins2.py
...
plugins0.py
...
...
- Load every handler from every module, namely *plugins0.py*, *plugins1.py* and *plugins2.py* in alphabetical order
(files) and definition order (handlers inside files):
Using *config.ini* file:
.. code-block:: ini
[plugins]
root = plugins
Using *Client*'s parameter:
.. code-block:: python
plugins = dict(root="plugins")
Client("my_account", plugins=plugins).run()
- Load only handlers defined inside *plugins2.py* and *plugins0.py*, in this order:
Using *config.ini* file:
.. code-block:: ini
[plugins]
root = plugins
include =
subfolder2.plugins2
plugins0
Using *Client*'s parameter:
.. code-block:: python
plugins = dict(
root="plugins",
include=[
"subfolder2.plugins2",
"plugins0"
]
)
Client("my_account", plugins=plugins).run()
- Load everything except the handlers inside *plugins2.py*:
Using *config.ini* file:
.. code-block:: ini
[plugins]
root = plugins
exclude = subfolder2.plugins2
Using *Client*'s parameter:
.. code-block:: python
plugins = dict(
root="plugins",
exclude=["subfolder2.plugins2"]
)
Client("my_account", plugins=plugins).run()
- Load only *fn3*, *fn1* and *fn2* (in this order) from *plugins1.py*:
Using *config.ini* file:
.. code-block:: ini
[plugins]
root = plugins
include = subfolder1.plugins1 fn3 fn1 fn2
Using *Client*'s parameter:
.. code-block:: python
plugins = dict(
root="plugins",
include=["subfolder1.plugins1 fn3 fn1 fn2"]
)
Client("my_account", plugins=plugins).run()
Load/Unload Plugins at Runtime
------------------------------
In the `previous section <#specifying-the-plugins-to-include>`_ we've explained how to specify which plugins to load and
which to ignore before your Client starts. Here we'll show, instead, how to unload and load again a previously
registered plugin at runtime.
Each function decorated with the usual ``on_message`` decorator (or any other decorator that deals with Telegram updates
) will be modified in such a way that, when you reference them later on, they will be actually pointing to a tuple of
*(handler: Handler, group: int)*. The actual callback function is therefore stored inside the handler's *callback*
attribute. Here's an example:
- ``plugins/handlers.py``
.. code-block:: python
:emphasize-lines: 5, 6
@Client.on_message(Filters.text & Filters.private)
def echo(client, message):
message.reply(message.text)
print(echo)
print(echo[0].callback)
- Printing ``echo`` will show something like ``(<MessageHandler object at 0x10e3abc50>, 0)``.
- Printing ``echo[0].callback``, that is, the *callback* attribute of the first element of the tuple, which is an
Handler, will reveal the actual callback ``<function echo at 0x10e3b6598>``.
Unloading
^^^^^^^^^
In order to unload a plugin, or any other handler, all you need to do is obtain a reference to it by importing the
relevant module and call :meth:`remove_handler() <pyrogram.Client.remove_handler>` Client's method with your function
name preceded by the star ``*`` operator as argument. Example:
- ``main.py``
.. code-block:: python
from plugins.handlers import echo
...
app.remove_handler(*echo)
The star ``*`` operator is used to unpack the tuple into positional arguments so that *remove_handler* will receive
exactly what is needed. The same could have been achieved with:
.. code-block:: python
handler, group = echo
app.remove_handler(handler, group)
Loading
^^^^^^^
Similarly to the unloading process, in order to load again a previously unloaded plugin you do the same, but this time
using :meth:`add_handler() <pyrogram.Client.add_handler>` instead. Example:
- ``main.py``
.. code-block:: python
from plugins.handlers import echo
...
app.add_handler(*echo)

View File

@@ -0,0 +1,50 @@
SOCKS5 Proxy
============
Pyrogram supports proxies with and without authentication. This feature allows Pyrogram to exchange data with Telegram
through an intermediate SOCKS5 proxy server.
Usage
-----
- To use Pyrogram with a proxy, simply append the following to your ``config.ini`` file and replace the values
with your own settings:
.. code-block:: ini
[proxy]
enabled = True
hostname = 11.22.33.44
port = 1080
username = <your_username>
password = <your_password>
To enable or disable the proxy without deleting your settings from the config file,
change the ``enabled`` value as follows:
- ``1``, ``yes``, ``True`` or ``on``: Enables the proxy
- ``0``, ``no``, ``False`` or ``off``: Disables the proxy
- Alternatively, you can setup your proxy without the need of the ``config.ini`` file by using the *proxy* parameter
in the Client class:
.. code-block:: python
from pyrogram import Client
app = Client(
session_name="example",
proxy=dict(
hostname="11.22.33.44",
port=1080,
username="<your_username>",
password="<your_password>"
)
)
app.start()
...
.. note:: If your proxy doesn't require authorization you can omit ``username`` and ``password`` by either leaving the
values blank/empty or completely delete the lines.

View File

@@ -0,0 +1,39 @@
Test Servers
============
If you wish to test your application in a separate environment, Pyrogram is able to authorize your account into
Telegram's test servers without hassle. All you need to do is start a new session (e.g.: "my_account_test") using
``test_mode=True``:
.. code-block:: python
from pyrogram import Client
with Client("my_account_test", test_mode=True) as app:
print(app.get_me())
.. note::
If this is the first time you login into test servers, you will be asked to register your account first.
Don't worry about your contacts and chats, they will be kept untouched inside the production environment;
accounts authorized on test servers reside in a different, parallel instance of a Telegram database.
Test Mode in Official Apps
--------------------------
You can also login yourself into test servers using official desktop apps, such as Webogram and TDesktop:
- **Webogram**: Login here: https://web.telegram.org/?test=1
- **TDesktop**: Open settings and type ``testmode``.
Test Numbers
------------
Beside normal numbers, the test environment allows you to login with reserved test numbers.
Valid phone numbers follow the pattern ``99966XYYYY``, where ``X`` is the DC number (1 to 3) and ``YYYY`` are random
numbers. Users with such numbers always get ``XXXXX`` as the confirmation code (the DC number, repeated five times).
.. important::
Do not store any important or private information in such test users' accounts; anyone can make use of the
simplified authorization mechanism and login at any time.

View File

@@ -0,0 +1,97 @@
Text Formatting
===============
Pyrogram, just like the `Telegram Bot API`_, natively supports basic Markdown and HTML formatting styles for text
messages and media captions.
Markdown style uses the same syntax as Telegram Desktop's and is enabled by default.
Beside bold, italic, and pre-formatted code, **Pyrogram does also support inline URLs and inline mentions of users**.
Markdown Style
--------------
To use this mode, pass :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or "markdown" in the *parse_mode* field when using
:obj:`send_message() <pyrogram.Client.send_message>`. Use the following syntax in your message:
.. code-block:: text
**bold text**
__italic text__
[inline URL](https://docs.pyrogram.ml/)
[inline mention of a user](tg://user?id=23122162)
`inline fixed-width code`
```block_language
pre-formatted fixed-width code block
```
HTML Style
----------
To use this mode, pass :obj:`HTML <pyrogram.ParseMode.HTML>` or "html" in the *parse_mode* field when using
:obj:`send_message() <pyrogram.Client.send_message>`. The following tags are currently supported:
.. code-block:: text
<b>bold</b>, <strong>bold</strong>
<i>italic</i>, <em>italic</em>
<a href="http://docs.pyrogram.ml/">inline URL</a>
<a href="tg://user?id=23122162">inline mention of a user</a>
<code>inline fixed-width code</code>
<pre>pre-formatted fixed-width code block</pre>
.. note:: Mentions are only guaranteed to work if you have already met the user (in groups or private chats).
Examples
--------
- Markdown:
.. code-block:: python
app.send_message(
chat_id="haskell",
text=(
"**bold**, "
"__italic__, "
"[mention](tg://user?id=23122162), "
"[URL](https://docs.pyrogram.ml), "
"`code`, "
"```"
"for i in range(10):\n"
" print(i)```"
)
)
- HTML:
.. code-block:: python
app.send_message(
chat_id="haskell",
text=(
"<b>bold</b>, "
"<i>italic</i>, "
"<a href=\"tg://user?id=23122162\">mention</a>, "
"<a href=\"https://pyrogram.ml/\">URL</a>, "
"<code>code</code>, "
"<pre>"
"for i in range(10):\n"
" print(i)"
"</pre>"
),
parse_mode="html"
)
.. _Telegram Bot API: https://core.telegram.org/bots/api#formatting-options

View File

@@ -0,0 +1,32 @@
Fast Crypto
===========
Pyrogram's speed can be *dramatically* boosted up by TgCrypto_, a high-performance, easy-to-install Telegram Crypto
Library specifically written in C for Pyrogram [#f1]_ as a Python extension.
TgCrypto is a replacement for the much slower PyAES and implements the crypto algorithms Telegram requires, namely
**AES-IGE 256 bit** (used in MTProto v2.0) and **AES-CTR 256 bit** (used for CDN encrypted files).
Installation
------------
.. code-block:: bash
$ pip3 install --upgrade tgcrypto
.. note:: Being a C extension for Python, TgCrypto is an optional but *highly recommended* dependency; when TgCrypto is
not detected in your system, Pyrogram will automatically fall back to PyAES and will show you a warning.
The reason about being an optional package is that TgCrypto requires some extra system tools in order to be compiled.
The errors you receive when trying to install TgCrypto are system dependent, but also descriptive enough to understand
what you should do next:
- **Windows**: Install `Visual C++ 2015 Build Tools <http://landinghub.visualstudio.com/visual-cpp-build-tools>`_.
- **macOS**: A pop-up will automatically ask you to install the command line developer tools.
- **Linux**: Install a proper C compiler (``gcc``, ``clang``) and the Python header files (``python3-dev``).
- **Termux (Android)**: Install ``clang`` and ``python-dev`` packages.
.. _TgCrypto: https://github.com/pyrogram/tgcrypto
.. [#f1] Although TgCrypto is intended for Pyrogram, it is shipped as a standalone package and can thus be used for
other Python projects too.

View File

@@ -0,0 +1,109 @@
Update Handling
===============
Calling `API methods`_ sequentially is cool, but how to react when, for example, a new message arrives? This page deals
with updates and how to handle them in Pyrogram. Let's have a look at how they work.
First, let's define what are these updates. Updates are simply events that happen in your Telegram account (incoming
messages, new members join, button presses, etc...), which are meant to notify you about a new specific state that
changed. These updates are handled by registering one or more callback functions in your app using
`Handlers <../pyrogram/Handlers.html>`_.
Each handler deals with a specific event and once a matching update arrives from Telegram, your registered callback
function will be called and its body executed.
Registering an Handler
----------------------
To explain how handlers work let's have a look at the most used one, the
:obj:`MessageHandler <pyrogram.MessageHandler>`, which will be in charge for handling :obj:`Message <pyrogram.Message>`
updates coming from all around your chats. Every other handler shares the same setup logic; you should not have troubles
settings them up once you learn from this section.
Using add_handler()
-------------------
The :meth:`add_handler() <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. First one: 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)
Second one: the :obj:`MessageHandler <pyrogram.MessageHandler>`. This object tells Pyrogram the function we defined
above must only handle updates that are in form of a :obj:`Message <pyrogram.Message>`:
.. code-block:: python
my_handler = MessageHandler(my_function)
Third: the method :meth:`add_handler() <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 dispatching phase
begins.
.. code-block:: python
app.add_handler(my_handler)
Last one, the :meth:`run() <pyrogram.Client.run>` method. What this does is simply calling
:meth:`start() <pyrogram.Client.start>` and a special method :meth:`idle() <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
----------------
All of the above will become quite verbose, especially in case you have lots of handlers to register. A much nicer way
to do so is by decorating your callback function with the :meth:`on_message() <pyrogram.Client.on_message>` decorator.
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
@app.on_message()
def my_handler(client, message):
print(message)
app.run()
.. note::
Due to how these decorators work in Pyrogram, they will wrap your defined callback function in a tuple consisting of
``(handler, group)``; this will be the value held by your function identifier (e.g.: *my_function* from the example
above).
In case, for some reason, you want to get your own function back after it has been decorated, you need to access
``my_function[0].callback``, that is, the *callback* field of the *handler* object which is the first element in the
tuple, accessed by bracket notation *[0]*.
.. _API methods: usage.html

View File

@@ -0,0 +1,85 @@
API Usage
=========
At this point, we have successfully `installed Pyrogram`_ and authorized_ our account and we are now pointing towards
the core of the library. It's time to start playing with the API!
Make API Method Calls
---------------------
Making API method calls with Pyrogram is very simple.
Here's an example we are going to examine:
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
app.start()
print(app.get_me())
app.send_message("me", "Hi, it's me!")
app.send_location("me", 51.500729, -0.124583)
app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI")
app.stop()
Let's begin by importing the Client class from the Pyrogram package:
.. code-block:: python
from pyrogram import Client
Now instantiate a new Client object, "my_account" is a session name of your choice:
.. code-block:: python
app = Client("my_account")
To actually make use of any method, the client has to be started:
.. code-block:: python
app.start()
Now, you can call any method you like:
.. code-block:: python
print(app.get_me()) # Print information about yourself
# Send messages to yourself:
app.send_message("me", "Hi!") # Text message
app.send_location("me", 51.500729, -0.124583) # Location
app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") # Sticker
Finally, when done, simply stop the client:
.. code-block:: python
app.stop()
Context Manager
---------------
You can also use Pyrogram's Client in a context manager with the ``with`` statement. The client will automatically
:meth:`start <pyrogram.Client.start>` and :meth:`stop <pyrogram.Client.stop>` gracefully, even in case of unhandled
exceptions in your code. The example above can be therefore rewritten in a much nicer way, this way:
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
with app:
print(app.get_me())
app.send_message("me", "Hi there! I'm using **Pyrogram**")
app.send_location("me", 51.500729, -0.124583)
app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI")
More examples can be found on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_.
.. _installed Pyrogram: ../intro/install.html
.. _authorized: ../intro/setup.html

View File

@@ -0,0 +1,194 @@
Using Filters
=============
So far we've seen how to register a callback function that executes every time a specific update comes from the server,
but there's much more than that to come.
Here we'll discuss about :class:`Filters <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.
Let's start right away with a simple example:
- This example will show you how to **only** handle messages containing an :obj:`Audio <pyrogram.Audio>` object and
ignore any other message. Filters are passed as the first argument of the decorator:
.. code-block:: python
:emphasize-lines: 4
from pyrogram import Filters
@app.on_message(Filters.audio)
def my_handler(client, message):
print(message)
- or, without decorators. Here filters are passed as the second argument of the handler constructor:
.. code-block:: python
:emphasize-lines: 8
from pyrogram import Filters, MessageHandler
def my_handler(client, message):
print(message)
app.add_handler(MessageHandler(my_handler, Filters.audio))
Combining Filters
-----------------
Filters can also be used in a more advanced way by inverting and combining more filters together using bitwise
operators ``~``, ``&`` and ``|``:
- Use ``~`` to invert a filter (behaves like the ``not`` operator).
- Use ``&`` and ``|`` to merge two filters (behave like ``and``, ``or`` operators respectively).
Here are some examples:
- Message is a **text** message **and** is **not edited**.
.. code-block:: python
@app.on_message(Filters.text & ~Filters.edited)
def my_handler(client, message):
print(message)
- Message is a **sticker** **and** is coming from a **channel or** a **private** chat.
.. code-block:: python
@app.on_message(Filters.sticker & (Filters.channel | Filters.private))
def my_handler(client, message):
print(message)
Advanced Filters
----------------
Some filters, like :meth:`command() <pyrogram.Filters.command>` or :meth:`regex() <pyrogram.Filters.regex>`
can also accept arguments:
- Message is either a */start* or */help* **command**.
.. code-block:: python
@app.on_message(Filters.command(["start", "help"]))
def my_handler(client, message):
print(message)
- Message is a **text** message or a media **caption** matching the given **regex** pattern.
.. code-block:: python
@app.on_message(Filters.regex("pyrogram"))
def my_handler(client, message):
print(message)
More handlers using different filters can also live together.
.. code-block:: python
@app.on_message(Filters.command("start"))
def start_command(client, message):
print("This is the /start command")
@app.on_message(Filters.command("help"))
def help_command(client, message):
print("This is the /help command")
@app.on_message(Filters.chat("PyrogramChat"))
def from_pyrogramchat(client, message):
print("New message in @PyrogramChat")
Custom Filters
--------------
Pyrogram already provides lots of built-in :class:`Filters <pyrogram.Filters>` to work with, but in case you can't find
a specific one for your needs or want to build a custom filter by yourself (to be used in a different kind of handler,
for example) you can use :meth:`Filters.create() <pyrogram.Filters.create>`.
.. note::
At the moment, the built-in filters are intended to be used with the :obj:`MessageHandler <pyrogram.MessageHandler>`
only.
An example to demonstrate how custom filters work is to show how to create and use one for the
:obj:`CallbackQueryHandler <pyrogram.CallbackQueryHandler>`. Note that callback queries updates are only received by
bots; create and `authorize your bot <../start/Setup.html#bot-authorization>`_, then send a message with an inline
keyboard to yourself. This allows you to test your filter by pressing the inline button:
.. code-block:: python
from pyrogram import InlineKeyboardMarkup, InlineKeyboardButton
app.send_message(
"username", # Change this to your username or id
"Pyrogram's custom filter test",
reply_markup=InlineKeyboardMarkup(
[[InlineKeyboardButton("Press me", b"pyrogram")]]
)
)
Basic Filters
^^^^^^^^^^^^^
For this basic filter we will be using only the first two parameters of :meth:`Filters.create() <pyrogram.Filters.create>`.
The code below creates a simple filter for hardcoded, static callback data. This filter will only allow callback queries
containing "Pyrogram" as data, that is, the function *func* you pass returns True in case the callback query data
equals to ``b"Pyrogram"``.
.. code-block:: python
static_data = Filters.create(
name="StaticdData",
func=lambda flt, callback_query: callback_query.data == b"Pyrogram"
)
The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example, the same
could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter's scope:
.. code-block:: python
def func(flt, callback_query):
return callback_query.data == b"Pyrogram"
static_data = Filters.create(
name="StaticData",
func=func
)
The filter usage remains the same:
.. code-block:: python
@app.on_callback_query(static_data)
def pyrogram_data(client, callback_query):
client.answer_callback_query(callback_query.id, "it works!")
Filters with Arguments
^^^^^^^^^^^^^^^^^^^^^^
A much cooler filter would be one that accepts "Pyrogram" or any other data as argument at usage time.
A dynamic filter like this will make use of the third parameter of :meth:`Filters.create() <pyrogram.Filters.create>`.
This is how a dynamic custom filter looks like:
.. code-block:: python
def dynamic_data(data):
return Filters.create(
name="DynamicData",
func=lambda flt, callback_query: flt.data == callback_query.data,
data=data # "data" kwarg is accessed with "filter.data"
)
And its usage:
.. code-block:: python
@app.on_callback_query(dynamic_data(b"Pyrogram"))
def pyrogram_data(client, callback_query):
client.answer_callback_query(callback_query.id, "it works!")

View File

@@ -0,0 +1,10 @@
Voice Calls
===========
A working proof-of-concept of Telegram voice calls using Pyrogram can be found here:
https://github.com/bakatrouble/pylibtgvoip. Thanks to `@bakatrouble <https://t.me/bakatrouble>`_.
.. note::
This page will be updated with more information once voice calls become eventually more usable and more integrated
in Pyrogram itself.