Skip to main content
This comprehensive example demonstrates advanced MadelineProto features. The code is from the official bot.php example.

Core Event Handler

<?php declare(strict_types=1);

use danog\MadelineProto\API;
use danog\MadelineProto\Broadcast\Progress;
use danog\MadelineProto\Broadcast\Status;
use danog\MadelineProto\EventHandler\Attributes\Cron;
use danog\MadelineProto\EventHandler\Attributes\Handler;
use danog\MadelineProto\EventHandler\Filter\FilterCommand;
use danog\MadelineProto\EventHandler\Filter\FilterRegex;
use danog\MadelineProto\EventHandler\Filter\FilterText;
use danog\MadelineProto\EventHandler\Message;
use danog\MadelineProto\ParseMode;
use danog\MadelineProto\SimpleEventHandler;

class MyEventHandler extends SimpleEventHandler
{
    public const ADMIN = "@me"; // !!! Change this to your username !!!
    
    private array $notifiedChats = [];

    /**
     * Get peer(s) where to report errors.
     */
    public function getReportPeers()
    {
        return [self::ADMIN];
    }
}

Lifecycle Methods

Startup Logic

public function onStart(): void
{
    $this->logger("The bot was started!");
    $this->logger($this->getFullInfo('MadelineProto'));
    
    $this->sendMessageToAdmins("The bot was started!");
}
The onStart() method runs once when the bot starts, perfect for initialization tasks.

Property Persistence

public function __sleep(): array
{
    return ['notifiedChats'];
}
Properties returned by __sleep() are automatically saved to the database (MySQL/Postgres/Redis if configured, session file otherwise).

Command Handlers

Echo Command

#[FilterCommand('echo')]
public function echoCmd(Message $message): void
{
    // Contains the arguments of the command
    $args = $message->commandArgs;
    
    $message->reply($args[0] ?? '');
}

Story Command (Admin Only)

#[FilterCommand('story')]
public function storyCommand(Message & FromAdmin $message): void
{
    if ($this->isSelfBot()) {
        $message->reply("Only users can post Telegram Stories!");
        return;
    }
    $media = $message->getReply(Message::class)?->media;
    if (!$media) {
        $message->reply("You should reply to a photo or video to repost it as a story!");
        return;
    }
    
    $this->stories->sendStory(
        peer: 'me',
        media: $media,
        caption: "This story was posted using [MadelineProto](https://t.me/MadelineProto)!",
        parse_mode: ParseMode::MARKDOWN,
        privacy_rules: [['_' => 'inputPrivacyValueAllowAll']]
    );
}

Reaction Commands

#[FilterCommand('react')]
public function reactCommand(Message&IsReply $message): void
{
    $message->getReply(Message::class)->addReaction('👌');
}

#[FilterCommand('unreact')]
public function unreactCommand(Message&IsReply $message): void
{
    $message->getReply(Message::class)->delReaction('👌');
}

Filter Examples

Text Filters

#[FilterText('test')]
public function pingCommand(Message $message): void
{
    $message->reply('test reply');
}

#[FilterTextCaseInsensitive('hi')]
public function pingCommandCaseInsensitive(Message $message): void
{
    $message->reply('hello');
}

Regex Filter

#[FilterRegex('/.*(mt?proto)[^.]?.*/i')]
public function testRegex(Incoming & Message $message): void
{
    $message->reply("Did you mean to write MadelineProto instead of ".$message->matches[1].'?');
}
Regex matches are available in $message->matches array.

Broadcasting

Broadcast Command

#[FilterCommand('broadcast')]
public function broadcastCommand(Message & FromAdmin $message): void
{
    if (!$message->replyToMsgId) {
        $message->reply("You should reply to the message you want to broadcast.");
        return;
    }
    $this->broadcastForwardMessages(
        from_peer: $message->senderId,
        message_ids: [$message->replyToMsgId],
        drop_author: true,
        pin: true,
    );
}

Progress Tracking

private int $lastLog = 0;

#[Handler]
public function handleBroadcastProgress(Progress $progress): void
{
    if (time() - $this->lastLog > 5 || $progress->status === Status::FINISHED) {
        $this->lastLog = time();
        $this->sendMessageToAdmins((string) $progress);
    }
}
Broadcast progress updates are sent automatically. Track them with a Progress handler.

File Handling

#[FilterCommand('dl')]
public function downloadLink(Incoming&Message $message): void
{
    $reply = $message->getReply(Message::class);
    if (!$reply?->media) {
        $message->reply("This command must reply to a media message!");
        return;
    }
    $reply->reply("Download link: ".$reply->media->getDownloadLink());
}
The bot must be started via web for download links to work, or configure a download script URL in settings.

Cron Jobs

#[Cron(period: 60.0)]
public function cron1(): void
{
    $this->sendMessageToAdmins("The bot is online, current time ".date(DATE_RFC850)."!");
}
Cron functions execute periodically. The period is specified in seconds.

Event-Specific Handlers

Photo Changed Events

#[Handler]
public function logPhotoChanged(Incoming&DialogPhotoChanged $message): void
{
    if ($message->photo) {
        $message->reply("Nice! Here's a download link for the photo: ".$message->photo->getDownloadLink());
    } else {
        $message->reply("Hmm, why did you delete the group photo?");
    }
}

Auto-Comment on Channel Posts

#[Handler]
public function makeComment(Incoming&ChannelMessage $message): void
{
    if ($this->isSelfBot()) {
        return;
    }
    $message->getDiscussion()->reply(
        message: "This comment is powered by [MadelineProto](https://t.me/MadelineProto)!",
        parseMode: ParseMode::MARKDOWN
    );
}

Configuration

Database Setup

use danog\MadelineProto\Settings;
use danog\MadelineProto\Settings\Database\Mysql;
use danog\MadelineProto\Settings\Database\Postgres;
use danog\MadelineProto\Settings\Database\Redis;

$settings = new Settings;

// Redis
$settings->setDb((new Redis)->setDatabase(0)->setPassword('pony'));

// PostgreSQL
$settings->setDb((new Postgres)->setDatabase('MadelineProto')->setUsername('daniil')->setPassword('pony'));

// MySQL
$settings->setDb((new Mysql)->setDatabase('MadelineProto')->setUsername('daniil')->setPassword('pony'));

Starting the Bot

// For users or bots
MyEventHandler::startAndLoop('bot.madeline', $settings);

// For bots only (with token)
MyEventHandler::startAndLoopBot('bot.madeline', 'bot token', $settings);

Complete Working Example

See the full source code at: examples/bot.php

Next Steps

Build docs developers (and LLMs) love