mirror of
https://github.com/pyrogram/pyrogram
synced 2025-08-30 05:48:14 +00:00
Merge branch 'plugins' into develop
This commit is contained in:
commit
9e159a3f50
@ -84,6 +84,7 @@ To get started, press the Next button.
|
|||||||
|
|
||||||
resources/UpdateHandling
|
resources/UpdateHandling
|
||||||
resources/UsingFilters
|
resources/UsingFilters
|
||||||
|
resources/Plugins
|
||||||
resources/AutoAuthorization
|
resources/AutoAuthorization
|
||||||
resources/CustomizeSessions
|
resources/CustomizeSessions
|
||||||
resources/TgCrypto
|
resources/TgCrypto
|
||||||
|
116
docs/source/resources/Plugins.rst
Normal file
116
docs/source/resources/Plugins.rst
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
Plugins
|
||||||
|
=======
|
||||||
|
|
||||||
|
Pyrogram embeds an **automatic** and lightweight plugin system that is meant to greatly 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**.
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
|
Prior to the plugin system, pluggable handlers were already possible. For instance, if you wanted to modularize your
|
||||||
|
applications, you had to do something 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 (e.g.: "pyrogram" -> "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()
|
||||||
|
|
||||||
|
...which 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?
|
||||||
|
|
||||||
|
Creating Plugins
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Setting up your Pyrogram project to accommodate plugins is as easy as creating a folder and putting your files full of
|
||||||
|
handlers inside.
|
||||||
|
|
||||||
|
.. note:: This is the same example application `as shown above <#introduction>`_, written using the 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])
|
||||||
|
|
||||||
|
- ``main.py``
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
Client("my_account").run()
|
||||||
|
|
||||||
|
The first important thing to note is the ``plugins`` folder, whose name is default and can be changed easily by setting
|
||||||
|
the ``plugins_dir`` parameter when creating a :obj:`Client <pyrogram.Client>`; you can put any python file in the
|
||||||
|
plugins folder and each file can contain any decorated function (handlers). Your Pyrogram Client instance (in the
|
||||||
|
``main.py`` file) will **automatically** scan the folder upon creation 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.
|
||||||
|
|
||||||
|
The ``main.py`` script is now at its bare minimum and cleanest state.
|
@ -33,6 +33,7 @@ import time
|
|||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from hashlib import sha256, md5
|
from hashlib import sha256, md5
|
||||||
|
from importlib import import_module
|
||||||
from signal import signal, SIGINT, SIGTERM, SIGABRT
|
from signal import signal, SIGINT, SIGTERM, SIGABRT
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ from pyrogram.api.errors import (
|
|||||||
PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned,
|
PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned,
|
||||||
VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate)
|
VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate)
|
||||||
from pyrogram.client.handlers import DisconnectHandler
|
from pyrogram.client.handlers import DisconnectHandler
|
||||||
|
from pyrogram.client.handlers.handler import Handler
|
||||||
from pyrogram.crypto import AES
|
from pyrogram.crypto import AES
|
||||||
from pyrogram.session import Auth, Session
|
from pyrogram.session import Auth, Session
|
||||||
from .dispatcher import Dispatcher
|
from .dispatcher import Dispatcher
|
||||||
@ -140,6 +142,11 @@ class Client(Methods, BaseClient):
|
|||||||
|
|
||||||
config_file (``str``, *optional*):
|
config_file (``str``, *optional*):
|
||||||
Path of the configuration file. Defaults to ./config.ini
|
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 "./plugins". Set to None to completely disable plugins.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
@ -161,7 +168,8 @@ class Client(Methods, BaseClient):
|
|||||||
last_name: str = None,
|
last_name: str = None,
|
||||||
workers: int = BaseClient.WORKERS,
|
workers: int = BaseClient.WORKERS,
|
||||||
workdir: str = BaseClient.WORKDIR,
|
workdir: str = BaseClient.WORKDIR,
|
||||||
config_file: str = BaseClient.CONFIG_FILE):
|
config_file: str = BaseClient.CONFIG_FILE,
|
||||||
|
plugins_dir: str or None = BaseClient.PLUGINS_DIR):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.session_name = session_name
|
self.session_name = session_name
|
||||||
@ -184,6 +192,7 @@ class Client(Methods, BaseClient):
|
|||||||
self.workers = workers
|
self.workers = workers
|
||||||
self.workdir = workdir
|
self.workdir = workdir
|
||||||
self.config_file = config_file
|
self.config_file = config_file
|
||||||
|
self.plugins_dir = plugins_dir
|
||||||
|
|
||||||
self.dispatcher = Dispatcher(self, workers)
|
self.dispatcher = Dispatcher(self, workers)
|
||||||
|
|
||||||
@ -219,6 +228,7 @@ class Client(Methods, BaseClient):
|
|||||||
|
|
||||||
self.load_config()
|
self.load_config()
|
||||||
self.load_session()
|
self.load_session()
|
||||||
|
self.load_plugins()
|
||||||
|
|
||||||
self.session = Session(
|
self.session = Session(
|
||||||
self,
|
self,
|
||||||
@ -968,6 +978,44 @@ class Client(Methods, BaseClient):
|
|||||||
if peer:
|
if peer:
|
||||||
self.peers_by_phone[k] = peer
|
self.peers_by_phone[k] = peer
|
||||||
|
|
||||||
|
def load_plugins(self):
|
||||||
|
if self.plugins_dir is not None:
|
||||||
|
try:
|
||||||
|
dirs = os.listdir(self.plugins_dir)
|
||||||
|
except FileNotFoundError:
|
||||||
|
if self.plugins_dir == Client.PLUGINS_DIR:
|
||||||
|
log.info("No plugin loaded: default directory is missing")
|
||||||
|
else:
|
||||||
|
log.warning('No plugin loaded: "{}" directory is missing'.format(self.plugins_dir))
|
||||||
|
else:
|
||||||
|
plugins_dir = self.plugins_dir.lstrip("./").replace("/", ".")
|
||||||
|
plugins_count = 0
|
||||||
|
|
||||||
|
for i in dirs:
|
||||||
|
module = import_module("{}.{}".format(plugins_dir, i.split(".")[0]))
|
||||||
|
|
||||||
|
for j in dir(module):
|
||||||
|
# noinspection PyBroadException
|
||||||
|
try:
|
||||||
|
handler, group = getattr(module, j)
|
||||||
|
|
||||||
|
if isinstance(handler, Handler) and isinstance(group, int):
|
||||||
|
self.add_handler(handler, group)
|
||||||
|
|
||||||
|
log.info('{}("{}") from "{}/{}" loaded in group {}'.format(
|
||||||
|
type(handler).__name__, j, self.plugins_dir, i, group)
|
||||||
|
)
|
||||||
|
|
||||||
|
plugins_count += 1
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
log.warning('Successfully loaded {} plugin{} from "{}"'.format(
|
||||||
|
plugins_count,
|
||||||
|
"s" if plugins_count > 1 else "",
|
||||||
|
self.plugins_dir
|
||||||
|
))
|
||||||
|
|
||||||
def save_session(self):
|
def save_session(self):
|
||||||
auth_key = base64.b64encode(self.auth_key).decode()
|
auth_key = base64.b64encode(self.auth_key).decode()
|
||||||
auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)]
|
auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)]
|
||||||
|
@ -52,6 +52,7 @@ class BaseClient:
|
|||||||
WORKERS = 4
|
WORKERS = 4
|
||||||
WORKDIR = "."
|
WORKDIR = "."
|
||||||
CONFIG_FILE = "./config.ini"
|
CONFIG_FILE = "./config.ini"
|
||||||
|
PLUGINS_DIR = "./plugins"
|
||||||
|
|
||||||
MEDIA_TYPE_ID = {
|
MEDIA_TYPE_ID = {
|
||||||
0: "thumbnail",
|
0: "thumbnail",
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import pyrogram
|
import pyrogram
|
||||||
|
from pyrogram.client.filters.filter import Filter
|
||||||
from ...ext import BaseClient
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
@ -36,7 +37,14 @@ class OnCallbackQuery(BaseClient):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
self.add_handler(pyrogram.CallbackQueryHandler(func, filters), group)
|
handler = pyrogram.CallbackQueryHandler(func, filters)
|
||||||
return func
|
|
||||||
|
if isinstance(self, Filter):
|
||||||
|
return pyrogram.CallbackQueryHandler(func, self), group if filters is None else filters
|
||||||
|
|
||||||
|
if self is not None:
|
||||||
|
self.add_handler(handler, group)
|
||||||
|
|
||||||
|
return handler, group
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import pyrogram
|
import pyrogram
|
||||||
|
from pyrogram.client.filters.filter import Filter
|
||||||
from ...ext import BaseClient
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
@ -36,7 +37,14 @@ class OnDeletedMessages(BaseClient):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
self.add_handler(pyrogram.DeletedMessagesHandler(func, filters), group)
|
handler = pyrogram.DeletedMessagesHandler(func, filters)
|
||||||
return func
|
|
||||||
|
if isinstance(self, Filter):
|
||||||
|
return pyrogram.DeletedMessagesHandler(func, self), group if filters is None else filters
|
||||||
|
|
||||||
|
if self is not None:
|
||||||
|
self.add_handler(handler, group)
|
||||||
|
|
||||||
|
return handler, group
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -28,7 +28,11 @@ class OnDisconnect(BaseClient):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
self.add_handler(pyrogram.DisconnectHandler(func))
|
handler = pyrogram.DisconnectHandler(func)
|
||||||
return func
|
|
||||||
|
if self is not None:
|
||||||
|
self.add_handler(handler)
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -17,11 +17,12 @@
|
|||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import pyrogram
|
import pyrogram
|
||||||
|
from pyrogram.client.filters.filter import Filter
|
||||||
from ...ext import BaseClient
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
class OnMessage(BaseClient):
|
class OnMessage(BaseClient):
|
||||||
def on_message(self, filters=None, group: int = 0):
|
def on_message(self=None, filters=None, group: int = 0):
|
||||||
"""Use this decorator to automatically register a function for handling
|
"""Use this decorator to automatically register a function for handling
|
||||||
messages. This does the same thing as :meth:`add_handler` using the
|
messages. This does the same thing as :meth:`add_handler` using the
|
||||||
:class:`MessageHandler`.
|
:class:`MessageHandler`.
|
||||||
@ -36,7 +37,14 @@ class OnMessage(BaseClient):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
self.add_handler(pyrogram.MessageHandler(func, filters), group)
|
handler = pyrogram.MessageHandler(func, filters)
|
||||||
return func
|
|
||||||
|
if isinstance(self, Filter):
|
||||||
|
return pyrogram.MessageHandler(func, self), group if filters is None else filters
|
||||||
|
|
||||||
|
if self is not None:
|
||||||
|
self.add_handler(handler, group)
|
||||||
|
|
||||||
|
return handler, group
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -21,7 +21,7 @@ from ...ext import BaseClient
|
|||||||
|
|
||||||
|
|
||||||
class OnRawUpdate(BaseClient):
|
class OnRawUpdate(BaseClient):
|
||||||
def on_raw_update(self, group: int = 0):
|
def on_raw_update(self=None, group: int = 0):
|
||||||
"""Use this decorator to automatically register a function for handling
|
"""Use this decorator to automatically register a function for handling
|
||||||
raw updates. This does the same thing as :meth:`add_handler` using the
|
raw updates. This does the same thing as :meth:`add_handler` using the
|
||||||
:class:`RawUpdateHandler`.
|
:class:`RawUpdateHandler`.
|
||||||
@ -32,7 +32,14 @@ class OnRawUpdate(BaseClient):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
self.add_handler(pyrogram.RawUpdateHandler(func), group)
|
handler = pyrogram.RawUpdateHandler(func)
|
||||||
return func
|
|
||||||
|
if isinstance(self, int):
|
||||||
|
return handler, group if self is None else group
|
||||||
|
|
||||||
|
if self is not None:
|
||||||
|
self.add_handler(handler, group)
|
||||||
|
|
||||||
|
return handler, group
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
Loading…
x
Reference in New Issue
Block a user