💻 Initial structure and a simple handler
Introduction
The structure of the module was described in the previous section. Now let's move on to its creation!
INFO
First read the PyroTGFork, this only describes the specifics of writing for PBModular
Step 1: Create module directory and configuration
Create a directory for your module (e.g., my_module) inside the main modules/ folder. Inside my_module, create:
- init.py: This file makes Python treat the directory as a package. It should import your main module class (see Step 2).
- config.yaml: This file defines metadata and settings for your module. It's preferred over the older
info.yaml
.
Example config.yaml:
# config.yaml
info:
name: MyModule # User-friendly name (single word recommended)
author: Your Name
version: 0.1.0
description: A brief description of what the module does.
src_url: https://github.com/your/repo # Optional: Link to source code
python: 3.11 # Optional: Recommended Python version (warns if different)
auto_load: true # Optional: Whether to load this module automatically (default: true)
# Optional: List of permissions needed (see Module Permissions)
permissions:
- use_db # Example: requests database access
# Optional: Custom configuration specific to your module
config:
key: "YOUR_DEFAULT_KEY"
feature_enabled: true
WARNING
Fallback: If config.yaml
is missing, the loader will look for info.yaml
(containing only info and permissions keys) for backward compatibility, but using config.yaml
is strongly recommended.
Step 2: Create the main module class
Create a Python file (e.g., main.py) in your module directory and define your main class inheriting from BaseModule
.
# main.py
from base.module import BaseModule, command
from pyrogram import Client
from pyrogram.types import Message
class MyModule(BaseModule):
# Module logic and handlers go here
pass
Now, make sure __init__.py
imports this class:
# __init__.py
from .main import MyModule
Step 3: Writing a command handler
Define an asynchronous method within your MyModule
class. The method should accept self
, and optionally client
(the Pyrogram Client instance), message
(or another update type), and sm_controller
(for FSM).
# main.py (inside MyModule class)
async def on_hello(self, client: Client, message: Message):
"""Replies with a friendly greeting.""" # Docstring used for auto-help
await message.reply("Hello World!")
Step 4: Registering the handler
Use decorators from base.module
to register your method as a handler.
@command(names, filters=None, fsm_state=None):
Registers a command handler.names:
A string or list of strings for the command name(s) (e.g., "hello" or ["hi", "hello"]).filters:
Optional Pyrogram filter (pyrogram.filters.Filter
) to add more specific conditions. Highly recommended for commands beyond simple triggers (e.g.,filters.private
orfilters.regex
).fsm_state:
Optional State or list of States for FSM control (see 🚦 Finite State Machine (FSM)).
@callback_query(filters=None, fsm_state=None):
Registers a callback query handler (for inline buttons).@message(filters=None, fsm_state=None):
Registers a general message handler. Using filters here is crucial to avoid conflicts and performance issues.@inline_query(filters=None, fsm_state=None):
Registers an inline query handler.
# main.py (inside MyModule class)
from base.module import command
from pyrogram import filters
@command("hello", filters=filters.private) # Only works in private chat
async def on_hello(self, client: Client, message: Message):
"""Replies with a friendly greeting."""
await message.reply("Hello World!")
@command("start", filters.regex(r"/start payload_\w+")) # Example with regex
async def on_start_with_payload(self, client: Client, message: Message):
"""Handles start command with specific payload"""
payload = message.text.split('_', 1)[1]
await message.reply(f"Received start with payload: {payload}")
INFO
The framework automatically handles command registration (command_registry) and permission checks (@allowed_for decorator and /allow_cmd command).
Advanced: Rate Limiting
You can easily add rate limiting to any handler using the @rate_limit
decorator. This should be placed before the handler decorator.
@rate_limit(rate=1, per=5, scope="user", queue=False):
rate
: Number of allowed calls within the time window.per
: The time window in seconds.scope
: The entity to limit ("user"
,"chat"
, or"global"
).queue
: IfTrue
, instead of dropping requests that exceed the limit, it will queue them and execute them after the required delay.
from base.module import command, rate_limit
@rate_limit(rate=1, per=10, scope="user") # 1 call per user every 10 seconds
@command("slow")
async def slow_command(self, message: Message):
"""A rate-limited command."""
await message.reply("You can use this command once every 10 seconds.")