Skip to content

💻 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:

  1. init.py: This file makes Python treat the directory as a package. It should import your main module class (see Step 2).
  2. config.yaml: This file defines metadata and settings for your module. It's preferred over the older info.yaml.

Example config.yaml:

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.

python
# 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:

python
# __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).

python
# 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 or filters.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.
python
# 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: If True, instead of dropping requests that exceed the limit, it will queue them and execute them after the required delay.
python
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.")