Merge branch 'develop' into asyncio
# Conflicts: # pyrogram/client/client.py
This commit is contained in:
commit
61e9762977
42
README.rst
42
README.rst
@ -3,6 +3,8 @@
|
||||
Pyrogram
|
||||
========
|
||||
|
||||
`A fully asynchronous variant is also available! <https://github.com/pyrogram/pyrogram/issues/181>`_
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pyrogram import Client, Filters
|
||||
@ -17,18 +19,20 @@ Pyrogram
|
||||
|
||||
app.run()
|
||||
|
||||
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for
|
||||
building custom Telegram applications that interact with the MTProto API as both User and Bot.
|
||||
**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 build custom Telegram applications that interact with the MTProto API as both user and bot.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
|
||||
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
|
||||
- **Easy**: You can install Pyrogram with pip and start building your app right away.
|
||||
- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way.
|
||||
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
|
||||
- **Updated** to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0.
|
||||
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
|
||||
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
|
||||
- **Documented**: Pyrogram API methods, types and public interfaces are well documented.
|
||||
- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
|
||||
- **Updated**, to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0.
|
||||
- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code.
|
||||
- **Comprehensive**: Execute any advanced action an official client is able to do, and even more.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
@ -47,7 +51,7 @@ Getting Started
|
||||
---------------
|
||||
|
||||
- The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.ml.
|
||||
- Reading Examples_ in this repository is also a good way for learning how things work.
|
||||
- Reading `Examples in this repository`_ is also a good way for learning how Pyrogram works.
|
||||
- Seeking extra help? Don't be shy, come join and ask our Community_!
|
||||
- For other requests you can send an Email_ or a Message_.
|
||||
|
||||
@ -67,7 +71,7 @@ Copyright & License
|
||||
.. _`Telegram`: https://telegram.org/
|
||||
.. _`Telegram API key`: https://docs.pyrogram.ml/start/ProjectSetup#api-keys
|
||||
.. _`Community`: https://t.me/PyrogramChat
|
||||
.. _`Examples`: https://github.com/pyrogram/pyrogram/tree/master/examples
|
||||
.. _`Examples in this repository`: https://github.com/pyrogram/pyrogram/tree/master/examples
|
||||
.. _`GitHub`: https://github.com/pyrogram/pyrogram/issues
|
||||
.. _`Email`: admin@pyrogram.ml
|
||||
.. _`Message`: https://t.me/haskell
|
||||
@ -83,17 +87,17 @@ Copyright & License
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<b>Telegram MTProto API Client Library for Python</b>
|
||||
<b>Telegram MTProto API Framework for Python</b>
|
||||
|
||||
<br>
|
||||
<a href="https://github.com/pyrogram/pyrogram/releases/latest">
|
||||
Download
|
||||
</a>
|
||||
•
|
||||
<a href="https://docs.pyrogram.ml">
|
||||
Documentation
|
||||
</a>
|
||||
•
|
||||
<a href="https://github.com/pyrogram/pyrogram/releases">
|
||||
Changelog
|
||||
</a>
|
||||
•
|
||||
<a href="https://t.me/PyrogramChat">
|
||||
Community
|
||||
</a>
|
||||
@ -104,7 +108,7 @@ Copyright & License
|
||||
</a>
|
||||
<a href="https://github.com/pyrogram/tgcrypto">
|
||||
<img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
|
||||
alt="TgCrypto">
|
||||
alt="TgCrypto Version">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@ -112,12 +116,12 @@ Copyright & License
|
||||
:target: https://pyrogram.ml
|
||||
:alt: Pyrogram
|
||||
|
||||
.. |description| replace:: **Telegram MTProto API Client Library for Python**
|
||||
.. |description| replace:: **Telegram MTProto API Framework for Python**
|
||||
|
||||
.. |scheme| image:: "https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30"
|
||||
.. |schema| image:: "https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30"
|
||||
:target: compiler/api/source/main_api.tl
|
||||
:alt: Scheme Layer
|
||||
:alt: Schema Layer
|
||||
|
||||
.. |tgcrypto| image:: "https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
|
||||
:target: https://github.com/pyrogram/tgcrypto
|
||||
:alt: TgCrypto
|
||||
:alt: TgCrypto Version
|
||||
|
@ -62,7 +62,7 @@ USER_IS_BOT A bot cannot send messages to other bots or to itself
|
||||
WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL
|
||||
STICKERSET_INVALID The requested sticker set is invalid
|
||||
PEER_FLOOD The method can't be used because your account is limited
|
||||
MEDIA_CAPTION_TOO_LONG The media caption is longer than 200 characters
|
||||
MEDIA_CAPTION_TOO_LONG The media caption is longer than 1024 characters
|
||||
USER_NOT_MUTUAL_CONTACT The user is not a mutual contact
|
||||
USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups
|
||||
API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side
|
||||
@ -86,4 +86,4 @@ TAKEOUT_REQUIRED The method must be invoked inside a takeout session
|
||||
MESSAGE_POLL_CLOSED You can't interact with a closed poll
|
||||
MEDIA_INVALID The media is invalid
|
||||
BOT_SCORE_NOT_MODIFIED The bot score was not modified
|
||||
USER_BOT_REQUIRED The method can be used by bots only
|
||||
USER_BOT_REQUIRED The method can be used by bots only
|
||||
|
|
@ -10,27 +10,28 @@ Welcome to Pyrogram
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<b>Telegram MTProto API Client Library for Python</b>
|
||||
<b>Telegram MTProto API Framework for Python</b>
|
||||
|
||||
<br>
|
||||
<a href="https://github.com/pyrogram/pyrogram/releases/latest">
|
||||
Download
|
||||
<a href="https://docs.pyrogram.ml">
|
||||
Documentation
|
||||
</a>
|
||||
•
|
||||
<a href="https://github.com/pyrogram/pyrogram">
|
||||
Source code
|
||||
<a href="https://github.com/pyrogram/pyrogram/releases">
|
||||
Changelog
|
||||
</a>
|
||||
•
|
||||
<a href="https://t.me/PyrogramChat">
|
||||
Community
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl">
|
||||
<a href="compiler/api/source/main_api.tl">
|
||||
<img src="https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30"
|
||||
alt="Scheme Layer">
|
||||
alt="Schema Layer">
|
||||
</a>
|
||||
<a href="https://github.com/pyrogram/tgcrypto">
|
||||
<img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
|
||||
alt="TgCrypto">
|
||||
alt="TgCrypto Version">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@ -48,25 +49,27 @@ Welcome to Pyrogram
|
||||
|
||||
app.run()
|
||||
|
||||
Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library.
|
||||
Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the framework.
|
||||
Contents are organized into self-contained topics and can be accessed from the sidebar, or by following them in order
|
||||
using the Next button at the end of each page. But first, here's a brief overview of what is this all about.
|
||||
|
||||
About
|
||||
-----
|
||||
|
||||
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for
|
||||
building custom Telegram applications that interact with the MTProto API as both User and Bot.
|
||||
**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 build custom Telegram applications that interact with the MTProto API as both user and bot.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
|
||||
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
|
||||
- **Easy**: You can install Pyrogram with pip and start building your app right away.
|
||||
- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way.
|
||||
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
|
||||
- **Updated** to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0.
|
||||
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
|
||||
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
|
||||
- **Documented**: Pyrogram API methods, types and public interfaces are well documented.
|
||||
- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
|
||||
- **Updated**, to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0.
|
||||
- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code.
|
||||
- **Comprehensive**: Execute any advanced action an official client is able to do, and even more.
|
||||
|
||||
To get started, press the Next button.
|
||||
|
||||
@ -85,6 +88,7 @@ To get started, press the Next button.
|
||||
resources/UpdateHandling
|
||||
resources/UsingFilters
|
||||
resources/MoreOnUpdates
|
||||
resources/ConfigurationFile
|
||||
resources/SmartPlugins
|
||||
resources/AutoAuthorization
|
||||
resources/CustomizeSessions
|
||||
|
90
docs/source/resources/ConfigurationFile.rst
Normal file
90
docs/source/resources/ConfigurationFile.rst
Normal file
@ -0,0 +1,90 @@
|
||||
Configuration File
|
||||
==================
|
||||
|
||||
As already mentioned in previous sections, Pyrogram can also 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 settings (private) 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>`_
|
@ -1,9 +1,9 @@
|
||||
Smart Plugins
|
||||
=============
|
||||
|
||||
Pyrogram embeds a **smart** (automatic) and lightweight plugin system that is meant to further simplify the organization
|
||||
of large projects and to provide a way for creating pluggable components that can be **easily shared** across different
|
||||
Pyrogram applications with **minimal boilerplate code**.
|
||||
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::
|
||||
|
||||
@ -13,7 +13,8 @@ Introduction
|
||||
------------
|
||||
|
||||
Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize
|
||||
your applications, you had to do something like this...
|
||||
your applications, you had to put your function definitions in separate files and register them inside your main script,
|
||||
like this:
|
||||
|
||||
.. note::
|
||||
|
||||
@ -63,19 +64,19 @@ your applications, you had to do something like this...
|
||||
|
||||
app.run()
|
||||
|
||||
...which 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:`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?
|
||||
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:
|
||||
Setting up your Pyrogram project to accommodate Smart Plugins is straightforward:
|
||||
|
||||
#. Create a new folder to store all the plugins (e.g.: "plugins").
|
||||
#. Put your files full of plugins inside.
|
||||
#. Enable plugins in your Client.
|
||||
#. 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::
|
||||
|
||||
@ -107,20 +108,252 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight
|
||||
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", plugins_dir="plugins").run()
|
||||
Client("my_account").run()
|
||||
|
||||
The first important thing to note is the new ``plugins`` folder, whose name is passed to the the ``plugins_dir``
|
||||
parameter when creating a :obj:`Client <pyrogram.Client>` in the ``main.py`` file — you can put *any python file* in
|
||||
there and each file can contain *any decorated function* (handlers) with only one limitation: within a single plugin
|
||||
file you must use different names for each decorated function. Your Pyrogram Client instance will **automatically**
|
||||
scan the folder upon creation to search for valid handlers and register them for you.
|
||||
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 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) namespace and things will work just the same.
|
||||
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``
|
||||
keys, 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 plugins 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 eleent 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)
|
@ -1,5 +1,5 @@
|
||||
TgCrypto
|
||||
========
|
||||
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.
|
||||
|
@ -159,10 +159,8 @@ class Client(Methods, BaseClient):
|
||||
config_file (``str``, *optional*):
|
||||
Path of the configuration file. Defaults to ./config.ini
|
||||
|
||||
plugins_dir (``str``, *optional*):
|
||||
Define a custom directory for your plugins. The plugins directory is the location in your
|
||||
filesystem where Pyrogram will automatically load your update handlers.
|
||||
Defaults to None (plugins disabled).
|
||||
plugins (``dict``, *optional*):
|
||||
TODO: doctrings
|
||||
|
||||
no_updates (``bool``, *optional*):
|
||||
Pass True to completely disable incoming updates for the current session.
|
||||
@ -199,7 +197,7 @@ class Client(Methods, BaseClient):
|
||||
workers: int = BaseClient.WORKERS,
|
||||
workdir: str = BaseClient.WORKDIR,
|
||||
config_file: str = BaseClient.CONFIG_FILE,
|
||||
plugins_dir: str = None,
|
||||
plugins: dict = None,
|
||||
no_updates: bool = None,
|
||||
takeout: bool = None):
|
||||
super().__init__()
|
||||
@ -225,7 +223,7 @@ class Client(Methods, BaseClient):
|
||||
self.workers = workers
|
||||
self.workdir = workdir
|
||||
self.config_file = config_file
|
||||
self.plugins_dir = plugins_dir
|
||||
self.plugins = plugins
|
||||
self.no_updates = no_updates
|
||||
self.takeout = takeout
|
||||
|
||||
@ -243,7 +241,14 @@ class Client(Methods, BaseClient):
|
||||
|
||||
@proxy.setter
|
||||
def proxy(self, value):
|
||||
self._proxy["enabled"] = True
|
||||
if value is None:
|
||||
self._proxy = None
|
||||
return
|
||||
|
||||
if self._proxy is None:
|
||||
self._proxy = {}
|
||||
|
||||
self._proxy["enabled"] = bool(value.get("enabled", True))
|
||||
self._proxy.update(value)
|
||||
|
||||
async def start(self):
|
||||
@ -1056,17 +1061,41 @@ class Client(Methods, BaseClient):
|
||||
setattr(self, option, getattr(Client, option.upper()))
|
||||
|
||||
if self._proxy:
|
||||
self._proxy["enabled"] = True
|
||||
self._proxy["enabled"] = bool(self._proxy.get("enabled", True))
|
||||
else:
|
||||
self._proxy = {}
|
||||
|
||||
if parser.has_section("proxy"):
|
||||
self._proxy["enabled"] = parser.getboolean("proxy", "enabled")
|
||||
self._proxy["enabled"] = parser.getboolean("proxy", "enabled", fallback=True)
|
||||
self._proxy["hostname"] = parser.get("proxy", "hostname")
|
||||
self._proxy["port"] = parser.getint("proxy", "port")
|
||||
self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None
|
||||
self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None
|
||||
|
||||
if self.plugins:
|
||||
self.plugins["enabled"] = bool(self.plugins.get("enabled", True))
|
||||
self.plugins["include"] = "\n".join(self.plugins.get("include", [])) or None
|
||||
self.plugins["exclude"] = "\n".join(self.plugins.get("exclude", [])) or None
|
||||
else:
|
||||
try:
|
||||
section = parser["plugins"]
|
||||
|
||||
self.plugins = {
|
||||
"enabled": section.getboolean("enabled", True),
|
||||
"root": section.get("root"),
|
||||
"include": section.get("include") or None,
|
||||
"exclude": section.get("exclude") or None
|
||||
}
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
for option in ["include", "exclude"]:
|
||||
if self.plugins[option] is not None:
|
||||
self.plugins[option] = [
|
||||
(i.split()[0], i.split()[1:] or None)
|
||||
for i in self.plugins[option].strip().split("\n")
|
||||
]
|
||||
|
||||
async def load_session(self):
|
||||
try:
|
||||
with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f:
|
||||
@ -1098,43 +1127,108 @@ class Client(Methods, BaseClient):
|
||||
self.peers_by_phone[k] = peer
|
||||
|
||||
def load_plugins(self):
|
||||
if self.plugins_dir is not None:
|
||||
plugins_count = 0
|
||||
if self.plugins.get("enabled", False):
|
||||
root = self.plugins["root"]
|
||||
include = self.plugins["include"]
|
||||
exclude = self.plugins["exclude"]
|
||||
|
||||
for path in Path(self.plugins_dir).rglob("*.py"):
|
||||
file_path = os.path.splitext(str(path))[0]
|
||||
import_path = []
|
||||
count = 0
|
||||
|
||||
while file_path:
|
||||
file_path, tail = os.path.split(file_path)
|
||||
import_path.insert(0, tail)
|
||||
if include is None:
|
||||
for path in sorted(Path(root).rglob("*.py")):
|
||||
module_path = os.path.splitext(str(path))[0].replace("/", ".")
|
||||
module = import_module(module_path)
|
||||
|
||||
import_path = ".".join(import_path)
|
||||
module = import_module(import_path)
|
||||
for name in vars(module).keys():
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
handler, group = getattr(module, name)
|
||||
|
||||
for name in dir(module):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
handler, group = getattr(module, name)
|
||||
if isinstance(handler, Handler) and isinstance(group, int):
|
||||
self.add_handler(handler, group)
|
||||
|
||||
if isinstance(handler, Handler) and isinstance(group, int):
|
||||
self.add_handler(handler, group)
|
||||
log.info('[LOAD] {}("{}") in group {} from "{}"'.format(
|
||||
type(handler).__name__, name, group, module_path))
|
||||
|
||||
log.info('{}("{}") from "{}" loaded in group {}'.format(
|
||||
type(handler).__name__, name, import_path, group))
|
||||
|
||||
plugins_count += 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if plugins_count > 0:
|
||||
log.warning('Successfully loaded {} plugin{} from "{}"'.format(
|
||||
plugins_count,
|
||||
"s" if plugins_count > 1 else "",
|
||||
self.plugins_dir
|
||||
))
|
||||
count += 1
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(self.plugins_dir))
|
||||
for path, handlers in include:
|
||||
module_path = root + "." + path
|
||||
warn_non_existent_functions = True
|
||||
|
||||
try:
|
||||
module = import_module(module_path)
|
||||
except ModuleNotFoundError:
|
||||
log.warning('[LOAD] Ignoring non-existent module "{}"'.format(module_path))
|
||||
continue
|
||||
|
||||
if "__path__" in dir(module):
|
||||
log.warning('[LOAD] Ignoring namespace "{}"'.format(module_path))
|
||||
continue
|
||||
|
||||
if handlers is None:
|
||||
handlers = vars(module).keys()
|
||||
warn_non_existent_functions = False
|
||||
|
||||
for name in handlers:
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
handler, group = getattr(module, name)
|
||||
|
||||
if isinstance(handler, Handler) and isinstance(group, int):
|
||||
self.add_handler(handler, group)
|
||||
|
||||
log.info('[LOAD] {}("{}") in group {} from "{}"'.format(
|
||||
type(handler).__name__, name, group, module_path))
|
||||
|
||||
count += 1
|
||||
except Exception:
|
||||
if warn_non_existent_functions:
|
||||
log.warning('[LOAD] Ignoring non-existent function "{}" from "{}"'.format(
|
||||
name, module_path))
|
||||
|
||||
if exclude is not None:
|
||||
for path, handlers in exclude:
|
||||
module_path = root + "." + path
|
||||
warn_non_existent_functions = True
|
||||
|
||||
try:
|
||||
module = import_module(module_path)
|
||||
except ModuleNotFoundError:
|
||||
log.warning('[UNLOAD] Ignoring non-existent module "{}"'.format(module_path))
|
||||
continue
|
||||
|
||||
if "__path__" in dir(module):
|
||||
log.warning('[UNLOAD] Ignoring namespace "{}"'.format(module_path))
|
||||
continue
|
||||
|
||||
if handlers is None:
|
||||
handlers = vars(module).keys()
|
||||
warn_non_existent_functions = False
|
||||
|
||||
for name in handlers:
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
handler, group = getattr(module, name)
|
||||
|
||||
if isinstance(handler, Handler) and isinstance(group, int):
|
||||
self.remove_handler(handler, group)
|
||||
|
||||
log.info('[UNLOAD] {}("{}") from group {} in "{}"'.format(
|
||||
type(handler).__name__, name, group, module_path))
|
||||
|
||||
count -= 1
|
||||
except Exception:
|
||||
if warn_non_existent_functions:
|
||||
log.warning('[UNLOAD] Ignoring non-existent function "{}" from "{}"'.format(
|
||||
name, module_path))
|
||||
|
||||
if count > 0:
|
||||
log.warning('Successfully loaded {} plugin{} from "{}"'.format(count, "s" if count > 1 else "", root))
|
||||
else:
|
||||
log.warning('No plugin loaded from "{}"'.format(root))
|
||||
|
||||
def save_session(self):
|
||||
auth_key = base64.b64encode(self.auth_key).decode()
|
||||
|
@ -125,3 +125,6 @@ class BaseClient:
|
||||
|
||||
async def get_chat_members(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def get_chat_members_count(self, *args, **kwargs):
|
||||
pass
|
||||
|
@ -67,6 +67,8 @@ class GetChatMember(BaseClient):
|
||||
)
|
||||
)
|
||||
|
||||
return pyrogram.ChatMember._parse(self, r.participant, r.users[0])
|
||||
users = {i.id: i for i in r.users}
|
||||
|
||||
return pyrogram.ChatMember._parse(self, r.participant, users)
|
||||
else:
|
||||
raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id))
|
||||
|
@ -84,9 +84,14 @@ class IterChatMembers(BaseClient):
|
||||
yielded = set()
|
||||
queries = [query] if query else QUERIES
|
||||
total = limit or (1 << 31) - 1
|
||||
filter = Filters.RECENT if total <= 10000 and filter == Filters.ALL else filter
|
||||
limit = min(200, total)
|
||||
|
||||
filter = (
|
||||
Filters.RECENT
|
||||
if self.get_chat_members_count(chat_id) <= 10000 and filter == Filters.ALL
|
||||
else filter
|
||||
)
|
||||
|
||||
if filter not in QUERYABLE_FILTERS:
|
||||
queries = [""]
|
||||
|
||||
|
@ -33,6 +33,19 @@ class ChatMember(PyrogramType):
|
||||
The member's status in the chat. Can be "creator", "administrator", "member", "restricted",
|
||||
"left" or "kicked".
|
||||
|
||||
date (``int``, *optional*):
|
||||
Date when the user joined, unix time. Not available for creator.
|
||||
|
||||
invited_by (:obj:`User <pyrogram.User>`, *optional*):
|
||||
Administrators and self member only. Information about the user who invited this member.
|
||||
In case the user joined by himself this will be the same as "user".
|
||||
|
||||
promoted_by (:obj:`User <pyrogram.User>`, *optional*):
|
||||
Administrators only. Information about the user who promoted this member as administrator.
|
||||
|
||||
restricted_by (:obj:`User <pyrogram.User>`, *optional*):
|
||||
Restricted and kicked only. Information about the user who restricted or kicked this member.
|
||||
|
||||
until_date (``int``, *optional*):
|
||||
Restricted and kicked only. Date when restrictions will be lifted for this user, unix time.
|
||||
|
||||
@ -86,6 +99,10 @@ class ChatMember(PyrogramType):
|
||||
client: "pyrogram.client.ext.BaseClient",
|
||||
user: "pyrogram.User",
|
||||
status: str,
|
||||
date: int = None,
|
||||
invited_by: "pyrogram.User" = None,
|
||||
promoted_by: "pyrogram.User" = None,
|
||||
restricted_by: "pyrogram.User" = None,
|
||||
until_date: int = None,
|
||||
can_be_edited: bool = None,
|
||||
can_change_info: bool = None,
|
||||
@ -104,6 +121,10 @@ class ChatMember(PyrogramType):
|
||||
|
||||
self.user = user
|
||||
self.status = status
|
||||
self.date = date
|
||||
self.invited_by = invited_by
|
||||
self.promoted_by = promoted_by
|
||||
self.restricted_by = restricted_by
|
||||
self.until_date = until_date
|
||||
self.can_be_edited = can_be_edited
|
||||
self.can_change_info = can_change_info
|
||||
@ -120,17 +141,18 @@ class ChatMember(PyrogramType):
|
||||
self.can_add_web_page_previews = can_add_web_page_previews
|
||||
|
||||
@staticmethod
|
||||
def _parse(client, member, user) -> "ChatMember":
|
||||
user = pyrogram.User._parse(client, user)
|
||||
def _parse(client, member, users) -> "ChatMember":
|
||||
user = pyrogram.User._parse(client, users[member.user_id])
|
||||
invited_by = pyrogram.User._parse(client, users[member.inviter_id]) if hasattr(member, "inviter_id") else None
|
||||
|
||||
if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)):
|
||||
return ChatMember(user=user, status="member", client=client)
|
||||
return ChatMember(user=user, status="member", date=member.date, invited_by=invited_by, client=client)
|
||||
|
||||
if isinstance(member, (types.ChannelParticipantCreator, types.ChatParticipantCreator)):
|
||||
return ChatMember(user=user, status="creator", client=client)
|
||||
|
||||
if isinstance(member, types.ChatParticipantAdmin):
|
||||
return ChatMember(user=user, status="administrator", client=client)
|
||||
return ChatMember(user=user, status="administrator", date=member.date, invited_by=invited_by, client=client)
|
||||
|
||||
if isinstance(member, types.ChannelParticipantAdmin):
|
||||
rights = member.admin_rights
|
||||
@ -138,6 +160,9 @@ class ChatMember(PyrogramType):
|
||||
return ChatMember(
|
||||
user=user,
|
||||
status="administrator",
|
||||
date=member.date,
|
||||
invited_by=invited_by,
|
||||
promoted_by=pyrogram.User._parse(client, users[member.promoted_by]),
|
||||
can_be_edited=member.can_edit,
|
||||
can_change_info=rights.change_info,
|
||||
can_post_messages=rights.post_messages,
|
||||
@ -155,7 +180,13 @@ class ChatMember(PyrogramType):
|
||||
|
||||
chat_member = ChatMember(
|
||||
user=user,
|
||||
status="kicked" if rights.view_messages else "restricted",
|
||||
status=(
|
||||
"kicked" if rights.view_messages
|
||||
else "left" if member.left
|
||||
else "restricted"
|
||||
),
|
||||
date=member.date,
|
||||
restricted_by=pyrogram.User._parse(client, users[member.kicked_by]),
|
||||
until_date=0 if rights.until_date == (1 << 31) - 1 else rights.until_date,
|
||||
client=client
|
||||
)
|
||||
|
@ -59,7 +59,7 @@ class ChatMembers(PyrogramType):
|
||||
total_count = len(members)
|
||||
|
||||
for member in members:
|
||||
chat_members.append(ChatMember._parse(client, member, users[member.user_id]))
|
||||
chat_members.append(ChatMember._parse(client, member, users))
|
||||
|
||||
return ChatMembers(
|
||||
total_count=total_count,
|
||||
|
Loading…
Reference in New Issue
Block a user